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



以下代码生成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。

你遇到了你认为是一个问题,但是,如果你在同一次执行中查看它们的哈希代码,你会发现它们并不相同,而是依赖于它们的使用顺序:

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上,而又被强加到调用RuntimeHelpers.GetHashCode(this)Object.GetHashCode上。

正是在这一点上,跟踪变得冷淡,但是,我的假设是该方法的内部工作原理会根据调用顺序创建一个新值,每个实例映射。

我通过使用两个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/共享源 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 和托管线程。

Program (.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

虽然只要哈希代码在程序生命周期内保持一致,我就可以理解随机性,但令我困扰的是,它并不总是随机的。

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

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

typeof使用缓存,因此对于给定类型,它将始终返回相同的实例,该实例模仿成员相等的行为,但事实并非如此:

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

至于原始问题,此结果可以适用于任何不覆盖GetHashCode的引用类型。 没有理由为什么GetHashCode的输出应该是随机的,它只需要对于不同内存地址的对象是不同的(并且在输出范围内分布良好(。 如果从同一起点按顺序分配内存地址,则从这些对象生成的哈希代码序列也将相同。

我应该补充一点,我不知道GetHashCode的实际基本实现,我只是从理论上讲,从引用类型的内存地址派生它是明智的。

为了回应Eric Ouellet的回答,我什至不会评论不正确的语法(哎呀,猜猜只是我做了(,但这些信息实际上是不准确的。

Visual Studio 中 C# 交互式控制台的结果证明 GetHashCode(( 在泛型类型上按预期工作。

证人:

> typeof(List<int>).GetHashCode()
42194754
> typeof(List<string>).GetHashCode()
39774547
> typeof(Stack<string>).GetHashCode()
59652943
> typeof(Stack<int>).GetHashCode()
5669220

相关内容

  • 没有找到相关文章

最新更新