Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does native implementation of ValueType.GetHashCode work?

Tags:

c#

struct

hash

I created two structures of TheKey type k1={17,1375984} and k2={17,1593144}. Obviosly the pointers in the second fields are different. But both get same hash code=346948941. Expected to see different hash codes. See the code below.

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

Output:
17
1375984
17
1593144
hash1=346948941
hash2=346948941

like image 405
tivadj Avatar asked May 08 '11 10:05

tivadj


People also ask

How is GetHashCode implemented?

For example, the implementation of the GetHashCode() method provided by the String class returns identical hash codes for identical string values. Therefore, two String objects return the same hash code if they represent the same string value.

What is return type of GetHashCode C#?

GetHashCode() Method is used to return the hash code for this instance. Syntax: public override int GetHashCode (); Return Value: This method returns the hash code for the current instance.

How do you create a hash code?

If you use eclipse, you can generate equals() and hashCode() using: Source -> Generate hashCode() and equals(). Using this function you can decide which fields you want to use for equality and hash code calculation, and Eclipse generates the corresponding methods.


2 Answers

It is a lot more complicated than meets the eye. For starters, give the key2 value a completely different string. Notice how the hash code is still the same:

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

Which is quite valid, the only requirement for a hash code is that the same value produces the same hash code. Different values don't have to produce different hash codes. That's not physically possible since a .NET hash code can only represent 4 billion distinct values.

Calculating the hash code for a struct is tricky business. The first thing the CLR does is check if the structure contains any reference type references or has gaps between the fields. A reference requires special treatment because the reference value is random. It is a pointer whose value changes when the garbage collector compacts the heap. Gaps in the structure layout are created because of alignment. A struct with a byte and an int has a 3 byte gap between the two fields.

If neither is the case then all the bits in the structure value are significant. The CLR quickly calculates the hash by xor-ing the bits, 32 at a time. This is a 'good' hash, all the fields in the struct participate in the hash code.

If the struct has fields of a reference type or has gaps then another approach is needed. The CLR iterates the fields of the struct and goes looking for one that is usable to generate a hash. A usable one is a field of a value type or an object reference that isn't null. As soon as it finds one, it takes the hash of that field, xors it with the method table pointer and quits.

In other words, only one field in the structure participates in the hash code calculation. Which is your case, only the id field is used. Which is why the string member value doesn't matter.

This is an obscure factoid that's obviously important to be aware of if you ever leave it up to the CLR to generate hash codes for a struct. By far the best thing to do is to just never do this. If you have to, then be sure to order the fields in the struct so that the first field gives you the best hash code. In your case, just swap the id and Name fields.


Another interesting tidbit, the 'good' hash calculation code has a bug. It will use the fast algorithm when the structure contains a System.Decimal. Problem is, the bits of a Decimal are not representative for its numeric value. Try this:

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!"); } 
like image 60
Hans Passant Avatar answered Sep 23 '22 23:09

Hans Passant


k1 and k2 contain the same values. Why are you surprised that they have the same hash code? It is contracted to return the same value for two objects that compare as equal.

like image 27
David Heffernan Avatar answered Sep 22 '22 23:09

David Heffernan