Why isn't .Except (LINQ) comparing things properly? (using IEquatable)

Viewed 8412

I have two collections of my own reference-type objects that I wrote my own IEquatable.Equals method for, and I want to be able to use LINQ methods on them.

So,

List<CandyType> candy = dataSource.GetListOfCandy();
List<CandyType> lollyPops = dataSource.GetListOfLollyPops();
var candyOtherThanLollyPops = candy.Except( lollyPops );

According to the documentation of .Except, not passing an IEqualityComparer should result in EqualityComparer.Default being used to compare objects. And the documentation for the Default comparer is this:

"The Default property checks whether type T implements the System.IEquatable generic interface and if so returns an EqualityComparer that uses that implementation. Otherwise it returns an EqualityComparer that uses the overrides of Object.Equals and Object.GetHashCode provided by T."

So, because I implement IEquatable for my object, it should use that and work. But, it doesn't. It doesn't work until I override GetHashCode. In fact, if I set a break point, my IEquatable.Equals method never gets executed. This makes me think that it's going with plan B according to its documentation. I understand that overriding GetHashCode is a good idea, anyway, and I can get this working, but I am upset that it is behaving in a way that isn't in line with what its own documentation stated.

Why isn't it doing what it said it would? Thank you.

5 Answers

I wrote a GenericEqualityComparer to be used on the fly for these types of methods: Distinct, Except, Intersect, etc.

Use as follows:

var results = list1.Except(list2, new GenericEqualityComparer<MYTYPE>((a, b) => a.Id == b.Id // OR SOME OTHER COMPARISON RESOLVING TO BOOLEAN));

Here's the class:

public class GenericEqualityComparer<T> : EqualityComparer<T>
{
    public Func<T, int> HashCodeFunc { get; set; }

    public Func<T, T, Boolean> EqualityFunc { get; set; }

    public GenericEqualityComparer(Func<T, T, Boolean> equalityFunc)
    {
        EqualityFunc = equalityFunc;
        HashCodeFunc = null;
    }

    public GenericEqualityComparer(Func<T, T, Boolean> equalityFunc, Func<T, int> hashCodeFunc) : this(equalityFunc)
    {
        HashCodeFunc = hashCodeFunc;
    }

    public override bool Equals(T x, T y)
    {
        return EqualityFunc(x, y);
    }

    public override int GetHashCode(T obj)
    {
        if (HashCodeFunc == null)
        {
            return 1;
        }
        else
        {
            return HashCodeFunc(obj);
        }
    }
}

I ran into this same problem, and debugging led me to a different answer than most. Most people point out that GetHashCode() must be implemented.

However, in my case - which was LINQ's SequenceEqual() - GetHashCode() was never called. And, despite the fact that every object involved was typed to a specific type T, the underlying problem was that SequenceEqual() called T.Equals(object other), which I had forgotten to implement, rather than calling the expected T.Equals(T other).

Related