2011-05-08 49 views
32

我创建了TheKey类型k1 = {17,1375984}和k2 = {17,1593144}的两种结构。 显然,第二个字段中的指针是不同的。但是两者都得到相同的散列码= 346948941。 预计会看到不同的哈希码。请参阅下面的代码。ValueType.GetHashCode的本地实现如何工作?

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); 
    } 
} 

输出:
HASH1 = 346948941
HASH2 = 346948941

+0

更多k1.Equals(k2)是真的 – empi 2011-05-08 10:20:22

回答

4

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

1

哈希码是根据结构/对象的状态(值内部)创建的。不是从它的保存位置。根据此:Why is ValueType.GetHashCode() implemented like it is?,值类型GetHashCode的默认行为struct是,将基于这些值返回散列值。我相信这是结构的特别正确的行为,它被认为是不可改变的。

72

它比眼睛复杂得多。对于初学者,给key2值一个完全不同的字符串。注意哈希代码仍然是相同的:

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

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

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

如果不是这种情况,那么结构值中的所有位都是有意义的。 CLR通过对位进行异或运算来快速计算散列,每次32位。这是一个'好'散列,结构中的所有字段都参与散列码。

如果结构具有引用类型的字段或有空位,则需要另一种方法。 CLR迭代结构的字段并寻找可用于生成散列的字段。可用的是值类型的字段或非空的对象引用。只要它找到一个,它就会使用该字段的散列值,并将其与方法表指针进行比较,然后退出

换句话说,结构中只有一个字段参与散列码计算。这是你的情况,只使用id字段。这就是为什么字符串成员值无关紧要的原因。

这是一个难以理解的factoid,明显的重要的是要意识到是否将它留给CLR来为结构生成哈希码。到目前为止,最好的做法是永远不要这样做。如果必须,那么一定要在结构中排序字段,以便第一个字段为您提供最佳的哈希码。在你的情况下,只需交换ID名称字段。


另一个有趣的消息,'好'散列计算代码有一个错误。当结构包含System.Decimal时,它将使用快速算法。问题是,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!"); 
} 
+0

谢谢@Hans。我改变了'TheKey'结构只有Name:string属性。正如你所说的CLR带有这个非空字段并从中做出散列。这些哈希值的概率很高(因为参考值不同)。但他们是平等的...看起来像基类库(BCL)认识到它是一个字符串字段,并从字符串的字符数组中进行散列。如果我有256个字符的字符串,他们都被哈希?! – tivadj 2011-05-10 18:44:39

+0

是的。使用粘贴箱显示您尝试的代码。 – 2011-05-10 21:14:29