【问题标题】:Why does System.Type.GetHashCode return the same value for all instances and types?为什么 System.Type.GetHashCode 为所有实例和类型返回相同的值?
【发布时间】:2011-11-18 05:20:59
【问题描述】:

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

【问题讨论】:

  • +1,有趣。虽然它们与我不匹配,但结果有时似乎不一致。
  • 当您同时查看它们的哈希码时,我在 .Net 4.0、3.5 和 2.0 上没有相同的行为。似乎 Type 哈希码从一个值开始,并且基于它们的使用顺序或外观(尽管我不确定)。
  • @sixlettervariables 您是处于调试模式还是发布模式,您是否使用附加的调试器 (VS) 对其进行测试?
  • @leppie,并非无关紧要,Eric 非常清楚地说明了 GetHashCode 的用例是什么,并且您不应该依赖于跨时间和跨系统的实现,请参阅规则 3。

标签: c# .net reflection


【解决方案1】:

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

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)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/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 和托管线程。

【讨论】:

  • 最让我好奇的是,为什么它具有我最初为在两台不同机器上运行的同一个程序集所赋予的价值。它们处于两个不同的状态,因此正如您在其他 cmets 中提到的那样,它们必须具有非常确定的特性。
  • @JNZ:嗯,我敢打赌框架有一些硬编码的种子值,所以它在你的机器上总是一样的。
  • 这没有任何意义,因为那样在两个不同的 .NET 4.0 系统上是一样的。在配置不同但当前运行 .NET 4.0 的两台不同的机器上是不同的,它们甚至都不是我的。
  • 为什么不呢,如果算法只是hash = hash + counterhash = seed作为初始化呢?
  • 大多数哈希算法都以众所周知的硬编码非随机种子开始。阅读Bob Jenkins' hash algorithms,他里面有很多不错的细节。我确定它是从一个固定的种子开始的,因为 461047280x02BF8098,巧合的是我得到的哈希码相同。
【解决方案2】:

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

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

【讨论】:

  • 真的很有趣。在我的系统上,对System.Type.GetHashCode 的第一次调用似乎在运行之间总是相同的。但是,后续调用对于不同的Type 实例(当然在不同类型上)会有所不同。我相信它以某种方式播种并产生了有偏见的结果。
  • @JNZ:答案是Type 不会覆盖GetHashCode,因此使用默认实现,它根据GetHashCode 的顺序以伪确定的方式分配它们调用。
  • @JNZ:你应该给其他回答者打勾。我的只是我自己的观察,没有试图回答这个问题:)
  • 老实说,这是一次错误的点击。我还不是想让任何人得到它,呵呵。只需 +1 就可以让您获得引导我进行调查性调查的信息。
【解决方案3】:

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

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

typeof 使用缓存,所以对于给定的类型,它总是会返回相同的实例,这模仿了成员相等的行为,但事实并非如此:

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

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

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

【讨论】:

    【解决方案4】:

    为了回应 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
    

    【讨论】:

      猜你喜欢
      • 2019-08-03
      • 1970-01-01
      • 2011-09-28
      • 2019-07-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-04
      相关资源
      最近更新 更多