Подтвердить что ты не робот

Реализация Object.GetHashCode()

Я читаю Эффективный С#, и есть комментарий о Object.GetHashCode(), который я не сделал понимать:

Object.GetHashCode() использует внутреннее поле в классе System.Object для генерации хэш-значения. Каждому создаваемому объекту присваивается уникальный ключ объекта, который хранится как целое, когда он создается.
Эти ключи начинаются с 1 и увеличиваются каждый раз, когда новый объект любого типа получает создано. Поле идентификации объекта устанавливается в конструкторе System.Object и не может быть изменено позже. Object.GetHashCode() возвращает это значение как хэш-код для данного объекта.

Я попытался посмотреть документацию Object.GetHashCode() и не нашел никакой информации об этом.

Я написал простой кусок кода для печати хеш-кода вновь созданных объектов:

using System;

namespace TestGetHashCode
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 100; i++)
            {
                object o = new object();
                Console.WriteLine(o.GetHashCode());
            }
        }
    }
}

Первые несколько напечатанных номеров:

37121646,
45592480,
57352375,
2637164,
41014879,
3888474,
25209742,
26966483,
31884011

Что не похоже на то, что

Эти ключи начинаются с 1 и увеличиваются каждый раз, когда создается новый объект любого типа... Object.GetHashCode() возвращает это значение

Затем, чтобы найти это "внутреннее поле в System.Object", я попытался использовать ReSharper декомпилированные источники, но код, который я нашел был

[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
[__DynamicallyInvokable]
public virtual int GetHashCode()
{
  return RuntimeHelpers.GetHashCode(this);
}

и снова используя декомпилированные источники, я обнаружил, что RuntimeHelpers.GetHashCode был реализован как

[SecuritySafeCritical]
[__DynamicallyInvokable]
[MethodImpl(MethodImplOptions.InternalCall)]
public static int GetHashCode(object o);

после атрибута MethodImpl кажется, что я не могу просмотреть реализацию, и для меня это тупик.

Может кто-нибудь объяснить комментарий автора (первая цитата)?

Что такое внутреннее поле в классе Object и как оно используется для реализации Object.GetHashCode()?

4b9b3361

Ответ 1

Хорошо, мне лучше написать это. Книга очень неточная. Значение для Object.GetHashCode() генерируется внутри CLR и вычисляется по требованию, всякий раз, когда GetHashCode() вызывается в первый раз. Я приведу код из дистрибутива SSCLI20, clr/src/vm/thread.h имеет функцию, которая производит номер, он выглядит так (отредактирован для удобочитаемости):

inline DWORD GetNewHashCode()
{
    // Every thread has its own generator for hash codes so that we won't get into a 
    // situation where two threads consistently give out the same hash codes.
    // Choice of multiplier guarantees period of 2**32
    // see Knuth Vol 2 p16 (3.2.1.2 Theorem A).
    DWORD multiplier = m_ThreadId*4 + 5;
    m_dwHashCodeSeed = m_dwHashCodeSeed*multiplier + 1;
    return m_dwHashCodeSeed;
}

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

Наличие переменной m_ThreadId в этом коде может использовать объяснение. Семь генераторов случайных чисел сохраняется для каждого отдельного потока. Трюк, чтобы избежать необходимости блокировки.

m_dwHashCodeSeed инициализируется в конструкторе Thread следующим образом:

   // Initialize this variable to a very different start value for each thread
   // Using linear congruential generator from Knuth Vol. 2, p. 102, line 24
   dwHashCodeSeed = dwHashCodeSeed * 1566083941 + 1;
   m_dwHashCodeSeed = dwHashCodeSeed;

с:

   static  DWORD dwHashCodeSeed = 123456789;