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