Friday, September 17, 2010

LINQ and IEquality

Linq provides several handy set functions - Union, Except, and Intersect. They even provide a way to override how equality comparisons are done by declaring a concrete IEqualityComparer.

When working with POCO objects though, it is quite cumbersome to have to provide comparers for every object. True, they could all implement an interface and go through a generic comparer, but a much easier option would be to allow anonymous functions to be defined. We need two things - a comparer class that takes delegates and some Linq extensions to make use of this.

Thankfully, Brendan Enrick already implemented a comparer that takes anonymous functions and runs them in IEqualityComparer.Equals()

/// <summary>
/// Generic equality comparer allowing lambda expressions
/// </summary>
/// <typeparam name="T"></typeparam>
/// <see cref="http://csharpfeeds.com/post/10941/LINQ_Your_Collections_with_IEqualityComparer_and_Lambda_Expressions.aspx"/>
public class LambdaComparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, T, bool> _comparer;
    private readonly Func<T, int> _hash;

    public LambdaComparer(Func<T, T, bool> comparer) : this(comparer, o => o.GetHashCode())
    { }

    public LambdaComparer(Func<T, T, bool> comparer, Func<T, int> hash)
    {
        if (comparer == null)
            throw new ArgumentNullException("comparer");
        if (hash == null)
            throw new ArgumentNullException("hash");

        _comparer = comparer;
        _hash = hash;
    }

    public bool Equals(T a, T b)
    {
        return _comparer(a, b);
    }

    public int GetHashCode(T o)
    {
        return _hash(o);
    }
}

Now we can call this as so:

myCollection.Intersect(otherCollection, new LambdaComparer<PocoType>((a, b) => a.Id == b.Id));

Having to declare a new LambdaComparer every time can be further simplified by adding some new Linq extensions that handle creating a new LambdaComparer for us.

public static class LinqExtensions
{
    public static IEnumerable<T> Except<T>(this IEnumerable<T> first,  IEnumerable<T> second, Func<T, T, bool> comparer)
    {
        return first.Except(second, new LambdaComparer<T>(comparer));
    }

    public static IEnumerable<T> Union<T>(this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer)
    {
        return first.Union(second, new LambdaComparer<T>(comparer));
    }

    public static IEnumerable<T> Intersect<T>(this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer)
    {
        return first.Intersect(second, new LambdaComparer<T>(comparer));
    }

    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> first, Func<T, T, bool> comparer)
    {
        return first.Distinct(new LambdaComparer<T>(comparer));
    }
}

This further simplifies the call and makes it a bit more elegant. We can do sets based on aribitary fields without having to declare a whole new comparison class.

myCollection.Intersect(otherCollection, (a, b) => a.Id == b.Id);
myCollection.Union(otherCollection, (a, b) => a.Name == b.Name);

No comments:

Post a Comment