C# - Вычисление GetHashCode

Рейтинг: 2Ответов: 1Опубликовано: 18.05.2023

Есть вопрос по работе метода GetHashCode. Читаю вроде как полезную книжку по C#, но об одном нюансе (который я не понимаю?) там не написано. Есть вот такой вот код:

    class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string SSN { get; set; } = "";
        public int Age { get; set; }

        public Person(string firstName, string lastName, int age)
        {
            FirstName = firstName;
            LastName = lastName;
            Age = age;
        }

        public Person() { }

        public override string ToString() => $"[First name: {FirstName}; Second name: {LastName}; Age: {Age}]";

        public override bool Equals(object? obj) => obj?.ToString() == this.ToString() ;

        // public override int GetHashCode() => this.ToString().GetHashCode() ;
    }

Здесь я переопределяю "стандартный" (?) метод GetHashCode из System.Object под свой класс, который считает хэш на основе строкового представления состояний объекта.

Запускаем следующий код (моя реализация закомменчена, отрабатывает "дефолтный" GetHashCode):

            Person Bernard = new Person("Bernard", "Cloud", 29);
            Person Kyle = new Person("Bernard", "Cloud", 29);

            Console.WriteLine(Bernard.ToString());
            Console.WriteLine(Kyle.ToString());
            Console.WriteLine(Bernard.Equals(Kyle));                // output: True => хэш должен быть одинаковый?

            Console.WriteLine(Bernard.GetHashCode());
            Console.WriteLine(Kyle.GetHashCode());

И видим, что хэши отличаются. У меня 2 вопроса.

  1. Почему в моей реализации GetHashCode каждый раз генерируются новые хэши при перезапуске программы.

  2. Почему при вызове стандартного GetHashCode хэши получаются разные, причем не изменяются при перезапуске.

У меня есть какие-то догадки, но это все смешалось в кашу. В моей реализации, я так понимаю, хэш вычисляется на основе состояний объекта (полей) и соответственно выводит либо равные, либо отличающиеся хэши.

А при перезапуске они меняются потому, что значение еще и зависит от того, где именно в памяти лежат объекты. А т.к. в C# автоматический сборщик мусора, при завершении экземпляры удаляются, а при запуске для них выделяется новая память.

Со стандартной реализацией вообще ничего не ясно. Почему хэши отличаются, может, и понятно: они считаются другим образом, не на основе строкового представления (как у меня)? А вот почему они еще и не изменяются при перезапуске программы.

Вообще, в моей реализации хэш считается для обычной строки string, в дефолтной "на вход" уже поступает object, а именно, экземпляр Person. Тут я подумал в разнице между типами и тем, как под них выделяется память, но вроде как это все относится к одному и тому же "Reference Types".

Ответы

▲ 5Принят

В дотнете хешкод - это функция, которая трансформирует объект в число. Это одностороннее преобразование, то есть, зная объект можно получить число, но зная число нельзя получить объект.

Функция, которая считает хешкод, должна имееть 3 характеристики (на слово не верьте, пишу по памяти)

  1. пока работает программа, одно и то же состояние объекта генерирует один и тот же хешкод. То есть одинаковые объекты должны иметь одинаковый хешкод. Потому при переопределении Equals надо также переопределять GetHashCode и наоборот.
  2. если у 2 объектов одинаковый хешкод, то объекты могут быть равны
  3. если у 2 объектов хешкод разный, то объекты точно не равны

Использование подобной функции позволяет очень быстро сравнить объекты. Например, если объекты не изменяемы, как строка например, то 2 строки с разными хешкодами точно не равны и никогда не будут равны. Если же хешкоды одинаковые у строк, то тогда надо строки сравнивать посимвольно, чтобы понять, равны ли они.

Второе использование хешкода - помогает распределить объекты в памяти хеш таблицы. Обычно хештаблицы используют массив для хранения ключей, и хешкод помогает понять, в какой индекс этого массива занести ключ. Потому для ключей в хештаблицах используют неизменяемые типы данных, ведь если сохранил ключ в хештаблицу, а потом поменял его состояние, то есть поменял и хешкод и больше этот ключ в таблице не найти.

По умолчанию, для класса Object хешкод можно сказать число случайное. Потому, если вы наследуете какой то класс от object, что является поведением по умолчанию, у каждого экземпляра класса будет какой то случайно сгенерированный хешкод.

Но если вы пытаетесь получить хешкод от строки, где функция генерации хешкода переопределена, то одинаковые строки выдадут одинаковый хешкод. При этом само значение хешкода может поменяться с перезапуском, тут главное, чтобы строки с разным кодом были разные и строки с одинаковым могли бы быть одинкаовыми.

Также случаются ситуации, когда 2 разных объека имеют одинаковый хешкод - такая ситуация называется коллизией. Потом объекты с одинаковым кодом надо сравнивать дополнительно. Нельзя сказать, что при одмнаковом коде объекты точно одинаковые, но они могут быть одмнаковые.

Теперь про перезапуски. Никогда не надейтесь, что при перезапуске хешкоды останутся такими же. Это не так. Майкрософт вообще оставляет за собой право переписать все алгоритмы по вычислению хешкодов. Хешкоды объеков не предназначены для долгосрочного хранения или для сравнения между процессами. Это прямым текстом написано в справке.