Как работает GetHashCode в .NET

В .NET метод GetHashCode устроен сложнее, чем «просто вернуть адрес объекта»: CLR хранит и кеширует хеш в заголовке объекта, использует разные алгоритмы для ссылочных, значимых типов, строк, делегатов и анонимных типов.

Что хранится в объекте

Как работает GetHashCode в .NET
  • У любого ссылочного объекта есть заголовок (Header) с двумя полями: указатель на таблицу методов типа (MethodTablePointer) и индекс блока синхронизации (SyncBlockIndex).

  • MethodTablePointer нужен для RTTI и виртуальных вызовов (через него работает GetType и диспетчеризация методов), SyncBlockIndex ― для работы lock/Monitor и других механизмов синхронизации.

Как менялся GetHashCode у ссылочных типов

  • В .NET 1.0–1.1 хеш ссылочного объекта просто брался как свободный индекс блока синхронизации (SyncBlock), который записывался в SyncBlockIndex; это создавало лишние структуры и делало хеши предсказуемыми и идущими подряд.

  • Начиная с .NET 2.0 хеш для Object генерируется потоко-специфичным линейным конгруэнтным генератором: у каждого потока своё семя и множитель, что снижает риск одинаковых последовательностей хешей между потоками.

Где хранится хеш‑код

  • При первом вызове GetHashCode CLR вычисляет значение и кладёт его в SyncBlockIndex; если объекту уже нужен SyncBlock для синхронизации, хеш переносится внутрь SyncBlock, а при освобождении блока обратно копируется в заголовок.

  • Таким образом, хеш стараются хранить либо в заголовке объекта, либо в связанном SyncBlock, чтобы не тратить дополнительную память на отдельное поле в каждом объекте.

Значимые типы: две стратегии

  • Если структура не содержит ссылочных полей и не имеет «пустот» между полями, CLR применяет быструю версию: XOR каждых 4 байт структуры, что задействует всё её содержимое; так, простые пары int,int получают разные хеши при разных значениях.

  • Если есть ссылочные поля или выравнивание даёт пробелы, используется медленная версия: CLR берёт первое экземплярное поле, считает его хеш, XOR‑ит его с указателем на тип этого поля и игнорирует остальные поля, что может давать одинаковые хеши для разных значений остальных полей.

Баг с decimal и вывод

  • В старых версиях .NET быстрый алгоритм давал разные хеши для чисел decimal, которые математически равны, но имеют разное внутреннее представление (например, 10.0m и 10.0000…m); это исправили только в .NET 4.

  • Поведение и производительность дефолтного GetHashCode для value types могут быть неожиданными, поэтому для пользовательских структур и классов рекомендуют явно переопределять GetHashCode (и Equals) под свою модель равенства.

Строки, делегаты и анонимные типы

  • String имеет собственный быстрый алгоритм (вариант djb2 с двумя накопителями), который при каждом вызове заново вычисляет хеш, не кэшируя его, чтобы не тратить память на дополнительное поле в каждом объекте строки; реализация менялась между версиями .NET, поэтому запрещено хранить такие хеши «на диск».

  • В .NET 4.5 добавили опцию «randomized string hashing» на домен приложений (Marvin32), чтобы одинаковые строки в разных доменах могли иметь разные хеш‑коды и лучше защищаться от атак по коллизиям.

  • Delegate по умолчанию возвращает хеш типа делегата (GetType().GetHashCode), поэтому делегаты одного типа с разными методами могут иметь одинаковый хеш.

  • MulticastDelegate переопределяет GetHashCode так, чтобы учитывать всю цепочку методов: обход _invocationList и комбинирование хешей каждого элемента, поэтому при различном количестве/наборе методов хеши различаются.

  • Для анонимных типов компилятор генерирует GetHashCode, который последовательно комбинирует хеш всех полей через умножение на константу и сложение, что делает их хорошо пригодными как ключи в LINQ‑операциях group/join.

  • Equals у анонимных типов переопределён (сравнение по значениям всех полей), а оператор == нет, поэтому Equals может вернуть true, а оператор == — false для двух разных экземпляров с одинаковыми полями.

Лига программистов

2.1K постов11.9K подписчика

Правила сообщества

- Будьте взаимовежливы, аргументируйте критику

- Приветствуются любые посты по тематике программирования

- Если ваш пост содержит ссылки на внешние ресурсы - он должен быть самодостаточным. Вариации на тему "далее читайте в моей телеге" будут удаляться из сообщества

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества