Search code examples
c#inheritancegethashcode

Should GetHashCode Depend on the Type?


Firstly, I'm using the GetHashCode algorithm described, here. Now, picture the following (contrived) example:

class Foo
{
    public Foo(int intValue, double doubleValue)
    {
        this.IntValue = intValue;
        this.DoubleValue = doubleValue;
    }

    public int IntValue { get; private set; }
    public double DoubleValue { get; private set; }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;

            hash = hash * 23 + IntValue.GetHashCode();
            hash = hash * 23 + DoubleValue.GetHashCode();
            return hash;
        }

    }
}

class DerivedFoo : Foo
{
    public DerivedFoo(int intValue, double doubleValue)
       : base(intValue, doubleValue)
    {

    }
}

If I have a Foo and a DerivedFoo with the same values for each of the properties they're going to have the same hash code. Which means I could have HashSet<Foo> or use the Distinct method in Linq and the two instances would be treated as if they were the same.

I'm probably just misunderstanding the use of GetHashCode but I would expect these the two instances to have different hash codes. Is this an invalid expectation or should GetHashCode use the type in the calculation? (Or should DerivedClass also override GetHashCode)?

P.S. I realize there are many, many questions on SO relating to this topic, but I've haven't spotted one that directly answers this question.


Solution

  • GetHashCode() is not supposed to guarantee uniqueness (though it helps for performance if as unique as possible).

    The main rule with GetHashCode() is that equivalent objects must have the same hash code, but that doesn't mean non-equivalent objects can't have the same hash code.

    If two objects have the same hash code, the Equals() method is then invoked to see if they are the same. Since the types are different (depending on how you coded your Equals overload of course) they will not be equal and thus it will be fine.

    Even if you had a different hash code algorithm for each type, there's still always a chance of a collision, thus the need for the Equals() check as well.

    Now given your example above, you do not implement Equals() this will make every object distinct regardless of the hash code because the default implementation of Equals() from object is a reference equality check.

    If you haven't, go ahead and override Equals() for each of your types as well (they can inherit your implementation of GetHashCode() if you like, or have new ones) and there you can make sure that the type of the compare-to object are the same before declaring them equal. And make sure Equals() and GetHashCode() are always implemented so that:

    • Objects that are Equals() must have same GetHashCode() results.
    • Objects with different GetHashCode() must not be Equals().