GetHashCode() is an interesting method, mainly because every object has it, but still it is used in so few places. The other 3 methods you get are: Equals(object), GetType() and ToString().

The other interesting method is Equals(object) (at least for this post), because this one is strongly related to the method in hand, namely - GetHashCode(). When checking anything related to .NET, it’s good the check what MSDN has to say about it.

Rules:

  • Equal objects must return the same hash code
  • Objects returning same hash code MAY NOT be equal (we can “exploit” this, check tips below)

Guidelines:

  • Subsequent calls to GetHashCode() should ALWAYS return the same result - you can’t be sure if your object was already put in a hash data structure
  • Do it fast - e.g. don’t try to fetch data from a database, so you can compute the object’s hash code
  • Don’t throw exceptions - if you don’t care about this method, just don’t override it. This goes for GetHashCode() as well as Equals(o) method
  • Don’t persist hash codes - they are may change from a one version of .NET Framework to another, as well as across AppDomains (refer to MSDN for more info)

Tips:

  • BCL types (int, float, double, string, DateTime, etc..) are properly implementing Equals(o) and GetHashCode() methods
  • When working on something and you don’t care about performance, you can always return the same (dummy) value. Of course, you should properly implement the Equals() method.
public override bool Equals(object o)
{
    // TODO: Compare all relevant fields/properties
}

public override int GetHashCode()
{
    return 0;
}
  • If you want to properly implement GetHashCode(), you can do it as simple as that:
class Person
{
    class Person(string name, int age)
    {
        this Name = name;
        this.Age = age;
    }

    public string Name { get; private set; }

    public int Age { get; private set; }

    public override Equals(object o)
    {
        var other = o as Person;
        if(other == null)
        {
            return false;
        }

        return this.Name == other.Name && this.Age == other.Age;
    }

    public override int GetHashCode()
    {
        return this.Name.GetHashCode() * 23 + this.Age.GetHashCode();
    }
}
  • Using anonymous classes will generate a proper hash code:
new object().GetHashCode(); // 46104728
new object().GetHashCode(); // 12289376
new {A = 1, B = 2}.GetHashCode(); // 1873708672
new {A = 1, B = 2}.GetHashCode(); // 1873708672
new {A = 1, C = 2}.GetHashCode(); // 35331381 <- Property name is taken into account!

Side effect:

  • When not both methods are not implemented - the CLR will generate a hash code somehow, for equality - it will use ReferenceEquals(objA, objB) to check if 2 objects are equals
  • When not implementing Equals(o) method - nothing much can happen, your objects will return the same hash code, but they won’t be considered equal unless they are the same object (as above)
  • When not implementing GetHashCode() method - this is the worst you can do, you even get a compiler warning. Imagine the following code:
class IntWrapper
{
    class IntWrapper(int value)
    {
        this.Value = value;
    }

    public int Value {get; private set; }

    public override bool Equals(object o)
    {
        return this.Value == ((IntWrapper)o).Value;
    }
}

var iw1 = new IntWrapper(1);
var iw2 = new IntWrapper(1);

iw1.Equals(iw2); // true

var hashSet = new HashSet<IntWrapper>();
hashSet.Add(iw1);
hashSet.Add(iw2); // hash set contains 2 elements, that's what everyone is "expecting", right?! :)

Sometimes you may want to use a 3rd party class which haven’t implemented the GetHashCode() and Equals(o) methods, but have no fear. CLR provides a very convenient constructor overload, namely this. You can implement the IEqualityComparer<T> interface.

class IntWrapperComparer : IEqualityComparer<IntWrapper>
{
    public bool Equals(IntWrapper x, IntWrapper y)
    {
        // IntWrapper's Equals(o) works as expected, just call it
        return x.Equals(y);
    }

    public int GetHashCode(IntWrapper obj)
    {
        // IntWrapper's GetHashCode() implementation is erroneous, provide a proper one
        return obj.Value.GetHashCode();
    }
}

var hashSet = new HashSet<IntWrapper>(new IntWrapperComparer());
hashSet.Add(iw1);
hashSet.Add(iw2); // now we have just 1 element ;)

Further reading: