Что возвращает метод GetHashCode в C#?

Ответ

Метод GetHashCode() возвращает 32-битное целое число (int), которое представляет хеш-код объекта. Это значение используется в качестве ключа в хеш-таблицах, таких как Dictionary<TKey, TValue> и HashSet<T>, для быстрого определения местоположения объекта и сравнения.

Ключевые контракты метода:

  1. Консистентность: Для одного и того же объекта, состояние которого не изменилось, метод должен возвращать одно и то же значение в течение всего времени жизни приложения.
  2. Согласованность с Equals: Если два объекта равны согласно методу Equals(object), то их хеш-коды, возвращаемые GetHashCode(), должны быть одинаковыми.
  3. Коллизии допускаются: Обратное утверждение неверно. Разные объекты (для которых Equals возвращает false) могут иметь одинаковый хеш-код (коллизия). Однако хорошая реализация стремится минимизировать коллизии для производительности.

Пример использования:

string str1 = "hello";
string str2 = "hello";
string str3 = "world";

Console.WriteLine(str1.GetHashCode() == str2.GetHashCode()); // True: одинаковые строки
Console.WriteLine(str1.GetHashCode() == str3.GetHashCode()); // False (с высокой вероятностью): разные строки

Важные замечания:

  • Для ссылочных типов по умолчанию GetHashCode() возвращает значение, основанное на идентификаторе потока и внутреннем индексе объекта в управляемой куче. Это не является адресом в памяти, но уникально для объекта в рамках процесса.
  • Для изменяемых (mutable) типов переопределять GetHashCode() крайне не рекомендуется. Если объект, используемый как ключ в словаре, изменяется после добавления, его хеш-код может измениться, и объект станет недоступен для поиска, что приведет к ошибкам.
  • При переопределении Equals(object) обязательно переопределяйте и GetHashCode(), чтобы соблюсти контракт. Хорошая реализация использует те же поля, что и Equals.

    public class Person
    {
    public int Id { get; init; }
    public string Name { get; init; }
    
    public override bool Equals(object obj) => obj is Person p && Id == p.Id && Name == p.Name;
    public override int GetHashCode() => HashCode.Combine(Id, Name); // Используем встроенный комбинатор
    }

Ответ 18+ 🔞

Да ты посмотри, какой тут материал подвезли — про GetHashCode(), ёбаный в рот! Ну ладно, разберём эту магию, как она есть, без прикрас.

Вот смотри, есть у каждого объекта в C# такая хитрая фишка — метод GetHashCode(). Он тебе выдаёт 32-битное число, типа как паспортный номер, но для хеш-таблиц. Эти таблицы, типа Dictionary или HashSet, на него смотрят и сразу понимают, где этот объект лежит, не перебирая всё подряд, как идиоты. Удобно, да?

Но тут, блядь, есть правила, нарушать которые — себя не уважать:

  1. Консистентность, ёпта! Если объект не менялся, его хеш-код должен быть как швейцарские часы — один и тот же всегда. Иначе тебя просто перестанут находить в словаре, и будешь там вечно висеть, как призрак.
  2. Согласованность с Equals — это святое! Если два объекта равны по Equals, то и хеш-коды у них обязаны совпадать. Иначе тебе пиздец — объект в словаре есть, а найти его не могут, потому что хеш разный. Это как искать ключи от квартиры, где деньги лежат, а они в другой куртке.
  3. Коллизии — это не смертельно. Обратное не работает: разные объекты могут случайно иметь одинаковый хеш. Это как два разных человека с одинаковым ИНН — бывает, но редко. Хорошая реализация старается, чтобы такое случалось как можно реже, иначе поиск замедлится до скорости улитки в смоле.

Вот пример, чтобы совсем понятно стало:

string str1 = "hello";
string str2 = "hello";
string str3 = "world";

Console.WriteLine(str1.GetHashCode() == str2.GetHashCode()); // True — одинаковые строки, хеш один
Console.WriteLine(str1.GetHashCode() == str3.GetHashCode()); // False (скорее всего) — строки разные

А теперь самое интересное, слушай сюда:

  • Для ссылочных типов по умолчанию GetHashCode() возвращает какую-то свою внутреннюю хуйню, основанную на потоке и индексе в куче. Это не адрес памяти, но в рамках процесса оно уникально. То есть два разных объекта в памяти почти всегда будут иметь разные хеши.
  • С изменяемыми типами — полный пиздец. Если ты переопределил GetHashCode() для класса, поля которого можно менять, и потом используешь его как ключ в словаре — ты самоубийца. Поменял поле — хеш изменился — объект в словаре потерялся навсегда. Ищи потом ветром надуло.
  • Главное правило, блядь! Если переопределяешь Equals(object), то обязательно переопределяй и GetHashCode(). И используй для расчёта хеша те же поля, что и в Equals. Иначе контракт нарушится, и всё полетит к чертям собачьим.

Смотри, как это делают адекватные люди:

public class Person
{
    public int Id { get; init; }
    public string Name { get; init; }

    public override bool Equals(object obj) => obj is Person p && Id == p.Id && Name == p.Name;
    // Берём те же поля, что и в Equals, и комбинируем их хеши
    public override int GetHashCode() => HashCode.Combine(Id, Name);
}

Вот и вся наука. Соблюдай контракты, не выёбывайся с изменяемыми ключами, и будет тебе счастье, а не коллизии ебаные.