为什么System.Type.GetHashCode为所有实例和types返回相同的值?

以下代码将生成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上它会为每种types产生不同的结果。 这个问题现在已经在一个以上的系统中被转载。 我正在使用.NET 4.0。

你已经遇到了你认为是个问题的地方,但是,如果你在同一个执行过程中查看他们的哈希码你会发现它们不完全相同,而是依赖于它们的使用顺序:

 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转移到了MemberInfo.GetHashCode ,这个代码再次被RuntimeHelpers.GetHashCode(this)到调用了RuntimeHelpers.GetHashCode(this) Object.GetHashCode上。

正是在这一点上,path变冷,但是,我的假设是,该方法的内部工作创build了一个新的值,根据调用的顺序映射到每个实例。

我通过运行与程序的两个实例上面相同的代码(添加一个属性来标识它们)后,testing了这个假设:

 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遵循这个实现,那么在对象的哈希码值中看到的任何差异都基于创build该实例的AppDomain和Managed Thread。

程序(.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 

虽然我可以理解随机性,只要散列码在程序生命周期内是一致的 ,这让我感到困扰,它并不总是随机的。

这是一个令人惊讶的结果,相对简单的解释。

Type使用EqualsGetHashCode的默认实现作为object 。 具体来说, Type实例在相同的实例(即在相同的内存地址)时是相等的。 同样,当对象是相同的实例时,它们的哈希码将是相等的。

typeof使用caching,所以对于一个给定的types,它将总是返回相同的实例,这模仿了成员相等的行为,但它不是:

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

至于原来的问题,这个结果可以适用于任何不重写GetHashCode引用types。 GetHashCode的输出应该是随机的,对于不同内存地址的对象(在输出范围内分布很好),不需要任何理由。 如果存储器地址是从相同的起始点顺序分配的,那么从这些对象产生的哈希码序列也是相同的。

我应该补充一点,我不知道GetHashCode的实际基本实现,我仅仅从理论上推断出从引用types的内存地址派生出来是明智的。