Whilst looking through a codebase, I saw implementations of IEqualityComparer<>. After thinking to myself that the need to create an entire implementation of IEqualityComparer<>
per use creates quite a bit of boilerplate for such a small amount of signal, I realised that creating a generic implementation of IEqualityComparer<>
that takes a definition of equality in its constructor would be very simple.
public class GenericEqualityComparer<T> : IEqualityComparer<T> { private readonly Func<T, T, bool> mEqualsFunc; private readonly Func<T, int> mGetHashCodeFunc; public GenericEqualityComparer(Func<T, T, bool> equalsFunc, Func<T, int> getHashCodeFunc) { if (equalsFunc == null) throw new ArgumentNullException("equalsFunc"); if (getHashCodeFunc == null) throw new ArgumentNullException("getHashCodeFunc"); mEqualsFunc = equalsFunc; mGetHashCodeFunc = getHashCodeFunc; } public bool Equals(T x, T y) { return mEqualsFunc(x, y); } public int GetHashCode(T obj) { return mGetHashCodeFunc(obj); } }
Creating and using an instance of this class is as simple as
public class TestClass { private static readonly GeneralEqualityComparer<Foo> mFooComparer = new GeneralEqualityComparer<Foo>( (x, y) => x.Id == y.Id, obj => obj.Id); public void GetDistinctFoos(IEnumerable<Foo> foos) { return foos.Distinct(mFooComparer); } }
However, I was a bit embarrassed when I told my boss, Tony Beveridge, about this great use of generics and Funcs I had thought of, and he told me he had actually implemented exactly the same class some months ago.
Its worth noting that EqualityComparer<T>.Default provides a default implementation using the Equals()
and GetHashCode()
functions of T
.
If you wanted to extend GenericEqualityComparer
so you don’t have to provide an implementation for GetHashCode()
, you can default mGetHashCodeFunc
to always return zero. This will force the Equals
function to always be called.