Почему System.Type.GetHashCode возвращает одинаковое значение для всех экземпляров и типов? - PullRequest
40 голосов
/ 18 ноября 2011

Следующий код выдает 46104728:

using System;

namespace TestApplication
{
    internal static class Program
    {
        private static void Main()
        {
            Type type = typeof(string);
            Console.WriteLine(type.GetHashCode());
            Console.ReadLine();
        }
    }
}

Но так же:

using System;

namespace TestApplication
{
    internal static class Program
    {
        private static void Main()
        {
            Type type = typeof(Program);
            Console.WriteLine(type.GetHashCode());
            Console.ReadLine();
        }
    }
 }

И все же на http://ideone.com он выдает разные результаты для каждого типа,Эта проблема была воспроизведена в более чем одной системе в настоящее время.Я сейчас использую .NET 4.0.

Ответы [ 5 ]

42 голосов
/ 18 ноября 2011

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

Console.WriteLine("{0} {1:08X}", typeof(string), typeof(string).GetHashCode());
Console.WriteLine("{0} {1:08X}", typeof(Program), typeof(Program).GetHashCode());
// System.String 02BF8098
// Program 00BB8560

Если я снова запустите ту же программу, поменяв местами их порядок:

Console.WriteLine("{0} {1:08X}", typeof(Program), typeof(Program).GetHashCode());
Console.WriteLine("{0} {1:08X}", typeof(string), typeof(string).GetHashCode());
// Program 02BF8098
// System.String 00BB8560

Это не проблема во время выполнения, так как возвращаемые значения ненарушать правила реализации Object.GetHashCode.

Но, как вы отметили, это поведение кажется любопытным!

Я углубился в исходный код и обнаружил, что реализация Type.GetHashCode навязана MemberInfo.GetHashCode, который снова навязан на Object.GetHashCode, который вызывает RuntimeHelpers.GetHashCode(this).

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

Я проверил эту гипотезу, выполнив один и тот же код выше с двумя экземплярами Program (после добавления свойства для их идентификации):

var b = new Program() { Name = "B" };
var a = new Program() { Name = "A" };
Console.WriteLine("{0} {1:08X}", a.Name, a.GetHashCode());
Console.WriteLine("{0} {1:08X}", b.Name, b.GetHashCode());
// A 02BF8098
// B 00BB8560

Таким образом, для сКлассы, которые явно не переопределяют Object.GetHashCode, экземплярам будет присвоено, казалось бы, предсказуемое хэш-значение, основанное на порядке, в котором они вызывают GetHashCode.


Обновление: Я пошели посмотрел на то, как Rotor / Shared Source CLI обрабатывает эту ситуацию, и я узнал, что реализация по умолчанию вычисляет и сохраняет хеш-код в блоке синхронизации для экземпляра объекта, таким образом гарантируя, что хеш-код генерируется только один раз.Вычисление по умолчанию для этого хэш-кода является тривиальным и использует начальное значение для каждого потока (обертывание - мое):

// ./sscli20/clr/src/vm/threads.h(938)
// 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).

Так что, если фактическая CLR следует этой реализации, могут возникнуть различия в значениях хеш-кодадля объектов основаны на AppDomain и Managed Thread, который создал экземпляр.

8 голосов
/ 18 ноября 2011

Программа (.NET 4, AnyCPU):

var st = typeof(string);
var pt = typeof(Program);
Console.WriteLine(st.GetHashCode());
Console.WriteLine(pt.GetHashCode());
Console.WriteLine(typeof(string).GetHashCode());
Console.WriteLine(typeof(Program).GetHashCode());
Console.ReadLine();

Прогон 1:

33156464
15645912
33156464
15645912

Прогон 2-6:

45653674
41149443
45653674
41149443

Прогон 7:

46104728
12289376
46104728
12289376

Прогон 8:

37121646
45592480
37121646
45592480

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

1 голос
/ 18 апреля 2017

Это удивительный результат с относительно простым объяснением.

Класс Type использует реализации по умолчанию для Equals и GetHashCode для object.В частности, Type экземпляры равны, когда они являются одним и тем же экземпляром (то есть с тем же адресом памяти).Точно так же, когда объекты являются одним и тем же экземпляром, их хэш-коды будут равны.

typeof использует кэширование, поэтому для данного типа он всегда будет возвращать один и тот же экземпляр, который имитирует поведение равенства членов, но это не так:

object.ReferenceEquals(typeof(string), typeof(string)) == true

Что касается исходного вопроса, этот результат может быть сохранен для любого ссылочного типа, который не переопределяет GetHashCode.Нет никаких причин, по которым вывод GetHashCode должен быть случайным, он должен быть разным только для объектов с разными адресами памяти (и хорошо распределен в диапазоне вывода).Если адреса памяти назначаются последовательно из одной и той же начальной точки, последовательность хеш-кодов, сгенерированных из этих объектов, также будет одинаковой.

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

0 голосов
/ 04 января 2018

В ответ на ответ Эрика Оуэллета я даже не буду комментировать неправильный синтаксис (упс, думаю, я только что это сделал), но эта информация фактически неточна.

Результаты с интерактивной консоли C # в VisualСтудия доказывает, что GetHashCode () работает должным образом на универсальных типах.

Свидетель:

> typeof(List<int>).GetHashCode()
42194754
> typeof(List<string>).GetHashCode()
39774547
> typeof(Stack<string>).GetHashCode()
59652943
> typeof(Stack<int>).GetHashCode()
5669220
0 голосов
/ 06 декабря 2017

Так же, как дополнительный справочник:

HashCode для 2 разных общих классов одинаковы:

List<int>.GetType().GetHashCode() == List<string>.GetType().GetHashCode()

Чтобы иметь другой HashCode:

List<int>.GetType().GetGenericTypeDefinition.GetHashCode() != List<string>.GetType().GetGenericTypeDefinition.GetHashCode()

Если вам когда-либо нужно сравнивать общие классы ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...