Delegate your equality comparisons

Marco Franssen /
3 min read • 429 words

When using Linq on your Entity Framework objects, you often need to distinct your query results. Therefore you need to implement an IEqualityComparer
for the more advance scenario's. For example if you want to distinct on a specific property, or maybe on multiple properties. However this forces you to write lots of infrastructure code to distinct each type.
You probably would end up with several equality compare classes like this.
public class ProductIdEqualityComparer : IEqualityComparer<Product>
{
public bool Equals(Product x, Product y)
{
return x.Id == y.Id;
}
public int GetHashCode(Product obj)
{
return obj.Id.GetHashCode();
}
}
public class ProductPriceEqualityComparer : IEqualityComparer<Product>
{
public bool Equals(Product x, Product y)
{
return x.Price == y.Price;
}
public int GetHashCode(Product obj)
{
return obj.Price.GetHashCode();
}
}
public class PersonLastNameEqualityComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
return x.LastName == y.LastName;
}
public int GetHashCode(Person obj)
{
return obj.LastName.GetHashCode();
}
}
However there is a solution which will save you the work to write all these classes. You will have to write only two classes. One will contain some extension methods, the other is a DelegateEqualityComparer
.
public static class CompareExtensions
{
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, Func<T, T, bool> equals, Func<T, int> hashCode)
{
return items.Distinct(new DelegateEqualityComparer<T>(equals, hashCode));
}
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, Func<T, T, bool> equals)
{
return items.Distinct(new DelegateEqualityComparer<T>(equals, null));
}
}
public class DelegateEqualityComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _equals;
private readonly Func<T, int> _hashCode;
public DelegateEqualityComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
{
_equals = equals;
_hashCode = hashCode;
}
public bool Equals(T x, T y)
{
return _equals(x, y);
}
public int GetHashCode(T obj)
{
if (_hashCode != null)
return _hashCode(obj);
return obj.GetHashCode();
}
}
Now you can simply distinct your query by providing a lambda. I tried it on IQueryable, but this doesn't work. Linq will generate some SQL to do the actual query. We didn't specified any code that can translate the equality comparer to SQL. If someone figures out how to make it work with IQueryable please let me know.
_products.Distinct((x, y) => x.Id == y.Id, x => x.Id.GetHashCode());
_products.Distinct((x, y) => x.Price == y.Price, x => x.Price.GetHashCode());
_persons.Distinct((x, y) => x.LastName == y.LastName, x => x.LastName.GetHashCode());
_persons.Distinct((x, y) => x.FirstName == y.FirstName, x => x.FirstName.GetHashCode());
_persons.Distinct((x, y) => x.Address.City == y.Address.City, x => x.Address.City.GetHashCode());
Share this article if you found it useful.