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);