【问题标题】:How does native implementation of ValueType.GetHashCode work?ValueType.GetHashCode 的本机实现如何工作?
【发布时间】:2011-08-21 01:42:45
【问题描述】:

我创建了两个结构 TheKey 类型 k1={17,1375984} 和 k2={17,1593144}。 显然,第二个字段中的指针是不同的。但两者都得到相同的哈希码=346948941。 预计会看到不同的哈希码。请参阅下面的代码。

struct TheKey
{
    public int id;
    public string Name;

    public TheKey(int id, string name)
    {
       this.id = id;
       Name = name;
   }
}

static void Main() {
    // assign two different strings to avoid interning
    var k1 = new TheKey(17, "abc");
    var k2 = new TheKey(17, new string(new[] { 'a', 'b', 'c' }));

    Dump(k1); // prints the layout of a structure
    Dump(k2);

    Console.WriteLine("hash1={0}", k1.GetHashCode());
    Console.WriteLine("hash2={0}", k2.GetHashCode());
}

unsafe static void Dump<T>(T s) where T : struct
{
    byte[] b = new byte[8];
    fixed (byte* pb = &b[0])
    {
        IntPtr ptr = new IntPtr(pb);
        Marshal.StructureToPtr(s, ptr, true);

        int* p1 = (int*)(&pb[0]); // first 32 bits
        int* p2 = (int*)(&pb[4]);

        Console.WriteLine("{0}", *p1);
        Console.WriteLine("{0}", *p2);
    }
}

输出:
17
1375984
17
1593144
哈希1=346948941
hash2=346948941

【问题讨论】:

  • 更何况 k1.Equals(k2) 是真的

标签: c# hash struct


【解决方案1】:

它比看上去要复杂得多。对于初学者,给 key2 值一个完全不同的字符串。注意哈希码仍然是一样的:

    var k1 = new TheKey(17, "abc");
    var k2 = new TheKey(17, "def");
    System.Diagnostics.Debug.Assert(k1.GetHashCode() == k2.GetHashCode());

这很有效,对哈希码的唯一要求是相同的值产生相同的哈希码。 不同的 值不必产生不同的哈希码。这在物理上是不可能的,因为 .NET 哈希码只能表示 40 亿个不同的值。

计算结构的哈希码是一项棘手的工作。 CLR 所做的第一件事是检查结构是否包含任何引用类型引用或字段之间是否存在间隙。参考需要特殊处理,因为参考值是随机的。它是一个指针,当垃圾收集器压缩堆时,它的值会发生变化。结构布局中的间隙是由于对齐而产生的。一个 byte 和一个 int 的结构在两个字段之间有 3 个字节的间隙。

如果两者都不是,那么结构值中的所有位都是有效的。 CLR 通过对位进行异或运算来快速计算散列,一次 32 个。这是一个“好”的哈希,结构中的所有字段都参与哈希码。

如果结构具有引用类型的字段或有间隙,则需要另一种方法。 CLR 迭代结构的字段并寻找可用于生成散列的字段。可用的字段是值类型的字段或不为空的对象引用。一旦找到,它就会获取该字段的哈希值,将其与方法表指针异或并退出

也就是说,结构中只有一个字段参与哈希码计算。这是您的情况,仅使用 id 字段。这就是字符串成员值无关紧要的原因。

这是一个晦涩难懂的事实,如果您将它留给 CLR 为结构生成哈希码,显然很重要。到目前为止,最好的办法就是永远不要这样做。如果必须,请确保对结构中的字段进行排序,以便第一个字段为您提供最佳哈希码。在您的情况下,只需交换 idName 字段。


另一个有趣的花絮,“好的”哈希计算代码有一个错误。当结构包含 System.Decimal 时,它将使用快速算法。问题是,十进制的位不能代表它的数值。试试这个:

struct Test { public decimal value; }

static void Main() {
    var t1 = new Test() { value = 1.0m };
    var t2 = new Test() { value = 1.00m };
    if (t1.GetHashCode() != t2.GetHashCode())
        Console.WriteLine("gack!");
}

【讨论】:

  • 谢谢@Hans。我将 TheKey 结构更改为只有 Name:string 属性。正如您所说,CLR 采用此非空字段并从中进行散列。很有可能这些哈希应该是不同的(因为 refs 是不同的)。但它们是平等的......看起来基类库(BCL)认识到它是一个string字段并从字符串的char数组中生成哈希值。如果我的字符串中有 256 个字符,它们都被散列了吗?!
【解决方案2】:

k1 和 k2 包含相同的值。为什么你会对它们具有相同的哈希码感到惊讶?它约定为两个比较相等的对象返回相同的值。

【讨论】:

    【解决方案3】:

    哈希码是根据结构/对象的状态(内部值)创建的。不是从哪里保存的。根据这个:Why is ValueType.GetHashCode() implemented like it is?GetHashCode 对于值类型的默认行为,struct 是基于值返回哈希值。而且我相信这是正确的行为,尤其是对于应该是不可变的结构。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-04
      • 2012-12-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-15
      相关资源
      最近更新 更多