4

I saw this code recently in a struct and I was wondering what base.GetHashCode actually does.

    public override int GetHashCode()
    {
        var hashCode = -592410294;
        hashCode = hashCode * -1521134295 + base.GetHashCode();
        hashCode = hashCode * -1521134295 + m_Value.GetHashCode();
        return hashCode;
    }
shA.t
  • 16,580
  • 5
  • 54
  • 111
Jonathan Allen
  • 68,373
  • 70
  • 259
  • 447

2 Answers2

7

The coreclr repo has this comment:

Action: Our algorithm for returning the hashcode is a little bit complex. We look for the first non-static field and get it's hashcode. If the type has no non-static fields, we return the hashcode of the type. We can't take the hashcode of a static member because if that member is of the same type as the original type, we'll end up in an infinite loop.

However, the code isn't there, and it looks like that's not quite what happens. Sample:

using System;

struct Foo
{
    public string x;
    public string y;
}

class Test
{
    static void Main()
    {
        Foo foo = new Foo();
        foo.x = "x";
        foo.y = "y";
        Console.WriteLine(foo.GetHashCode());
        Console.WriteLine("x".GetHashCode());
        Console.WriteLine("y".GetHashCode());
    }
}

Output on my box:

42119818
372029398
372029397

Changing the value of y doesn't appear to change the hash code of foo.

However, if we make the fields int values instead, then more than the first field affects the output.

In short: this is complex, and you probably shouldn't depend on it remaining the same across multiple versions of the runtime. Unless you're really, really confident that you don't need to use your custom struct as a key in a hash-based dictionary/set, I'd strongly recommend overriding GetHashCode and Equals (and implementing IEquatable<T> to avoid boxing).

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Using ints shows the value changing so yes, it is complex, but that is also why it is undocumented behavior. – Lasse V. Karlsen Oct 30 '17 at 09:02
  • It seems that when ints are involved (at least) there is a simple XOR of the field gethashcodes/values that is used, if one field = 24, another is 8, the third is 16, then the final result is the same as if I place any other values in there that together XOR to 0. – Lasse V. Karlsen Oct 30 '17 at 09:04
  • @LasseVågsætherKarlsen: Ah, I need to correct my answer then - I'd only seen the first field affect things. – Jon Skeet Oct 30 '17 at 09:05
  • 1
    @LasseVågsætherKarlsen: Edited. See if you think there's anything else you reckon I should amend. – Jon Skeet Oct 30 '17 at 09:10
  • No, my comment was only observational and when I discovered that the reference source is not representative I went back to leaning on the documentation, which explicitly *does not* state how this is implemented, only a hand-waving vague description. – Lasse V. Karlsen Oct 30 '17 at 11:16
1

The base class of a struct is the ValueType class, and the source code is online. They helpfully left a comment that describes how it works:

ValueType.GetHashCode:

/*=================================GetHashCode==================================
**Action: Our algorithm for returning the hashcode is a little bit complex.  We look
**        for the first non-static field and get it's hashcode.  If the type has no
**        non-static fields, we return the hashcode of the type.  We can't take the
**        hashcode of a static member because if that member is of the same type as
**        the original type, we'll end up in an infinite loop.
**Returns: The hashcode for the type.
**Arguments: None.
**Exceptions: None.
==============================================================================*/
[System.Security.SecuritySafeCritical]  // auto-generated
[ResourceExposure(ResourceScope.None)]
[MethodImplAttribute(MethodImplOptions.InternalCall)]
public extern override int GetHashCode();
Rufus L
  • 36,127
  • 5
  • 30
  • 43