-1

I have a class A that inherits from a class B and implements IEqualityComparer<A>. This means class A provides its own implementation of both Equals and GetHashCode methods. So far so good. The problem is that I don't understand why the code behaves in the following way:

debugger will only reach A's Equals implementation breakpoint if A's GetHashCode implementation returns this.GetHashCode() instead of obj.GetHashCode(), with "obj" being the parameter that GetHashCode's signature defines(a variable of type A, in my case).

Intuitively, I thought that I should return the hashcode of the object I received, but doing so makes the compiler ignore the instance's Equals implementation.

Why does this happen?

Code demonstration:

public class A : B, IEqualityComparer<A>
{
    public bool Equals(A x, A y)
    {
        //my implementation...
    }

    public int GetHashCode(A obj)
    {
        //return obj.GetHashCode(); -> this makes my Equals implementation above be ignored! Why?
        return this.GetHashCode(); -> my Equals implementation is used
    }
}
Veverke
  • 9,208
  • 4
  • 51
  • 95

2 Answers2

1

Implementing IEqualityComparer<T> doesn't override the base implementation of GetHashCode and Equals.

Implementing IEqualityComparer<T> allows you to supply an instance of the implementor as an equality comparer for T. This is a common parameter to several linq extensions and generic collection constructors.

Overriding Equals and GetHashCode effects the way instances of a class are tested for equality. Tapping into other implmentations that call Equals and GetHashCode, like the base = and != operators and linq extensions and generic collection constructors where you don't supply an alternative IEqualityComparer<T>.

These concepts are similar but serve different purposes, they are not partially interchangable.


Let me expand with an example,

public class A
{
    public string Value1 { get; set; }
    public int Value2 { get; set; }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            hash = (hash * 23) + 
                StringComparer.Ordinal.GetHashCode(this.Value1);
            hash = (hash * 23) + this.Value2;
            return hash;
        }
    }

    public override bool Equals(object obj)
    {
        var a = obj as A;
        if (a == null)
        {
            return false;
        }

        if (a.Value2 != this.Value2)
        {
            return false;
        }

        return StringComparer.Ordinal.Equals(
            a.Value1,
            this.Value1);
    }
}

This implementation of A correctly overrides Equals and GetHashCode, this change is sufficient to ensure that after calling the linq extension

var distinct = aSequneceOfA.Distinct();

distinct will not contain any instances that have both the same Value2 and ordinally comparable Value1. No other interface implementation is necessary to achieve this.


Now, suppose that in some situation I wasn't happy with this ordinal comparison for Value1, perhaps I require some case insensitivity. I might implement an new equality comparer.

public class AComparerInsensitive : IEqualityComparer<A>
{
    public bool Equals(A x, A y)
    {
        if (x == null)
        {
            return y == null;
        }

        if (y == null)
        {
            return false;
        }

        if (x.Value2 != y.Value2)
        {
            return false;
        }

        return StringComparer.CurrentCultureIgnoreCase.Equals(
            x.Value1,
            y.Value1)
    }

    public int GetHashCode(A a)
    {
        if (a == null)
        {
            return 0;
        }

        unchecked
        {
            int hash = 17;
            hash = (hash * 23) + 
                StringComparer.CurrentCultureIgnoreCase.GetHashCode(
                    a.Value1);
            hash = (hash * 23) + a.Value2;
            return hash;
        }
    }
}

This would allow me to call the alternative overload of Distinct,

var insensitivelyDistinct = aSequneceOfA.Distinct(
    new AComparerInsensitive());

This overload of distinct ingnores As overridden Equals and GetHashCode and uses AComparerInsensitive to perform the comparison.

Jodrell
  • 34,946
  • 5
  • 87
  • 124
1

It sounds like you are using the wrong interface. IEqualityComparer<> is typically used for a class that compares instances of other types.

Your type should simply implement IEquatable<A> and override Equals(object) and GetHashCode(). Note the signatures.

Like this:

public class A : B, IEquatable<A>
{
    public bool Equals(A other)
    {
       if (other == null || GetType() != other.GetType())
           return false;

       //your implementation
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as A);
    }

    public override int GetHashCode()
    {
        //your implementation
    }
}

Then you can do stuff like someEnumerableOfA.Distinct() and the Linq method will use your implementation.

The other option is to do:

public class A : B // no interfaces
{
}

public class AEqualComparer : IEqualityComparer<A>
{
    public bool Equals(A x, A y)
    {
       //your implementation
    }

    public int GetHashCode(A x)
    {
        //your implementation
    }
}

With this other option you need someEnumerableOfA.Distinct(new AEqualComparer ()).

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181