I was recently doing some work where I had to use Except and Insersect, but with some custom comparison logic. Let me illustrate by way of an example. If I had the following class
public class Widget { public int ID { get; set; } public string Name { get; set; } }
and created two lists like this
List<Widget> first = new List<Widget>() { new Widget() { ID = 1, Name = "Alpha"}, new Widget() { ID = 2, Name = "Beta"}, new Widget() { ID = 3, Name = "Gamma"}, }; List<Widget> second = new List<Widget>() { new Widget() { ID = 2, Name = "Foo"}, new Widget() { ID = 3, Name = "Bar"}, new Widget() { ID = 4, Name = "Baz"}, };
and tried to use Except like this
first.Except(second).ToList().ForEach(x => Console.WriteLine("ID=" + x.ID + ", " + x.Name));
I would get:
ID=1, Alpha ID=2, Beta ID=3, Gamma
Since I wanted to do a comparison on the ID property, what I really want is just the first item the list i.e. [ID=1, Alpha]. One way of achieving this to override Equals() on the Widget class, but this may not always be practical or even possible. Another option is to supply a custom IEqualityComparer<T>. To do this I would need to a define a class that implements Equals() and GetHashCode(), the two methods on IEqualityComparer<T> and supply an instance of this class as an additional parameter to Equals() or Intersect(). Unfortunately, I found neither of these practical since I had a number of classes that that I wanted to use with Except() or Intersect() and wanted a more elegant solution.
“Why not just use a lambda expression to express the comparison?”, I thought and came up with the following two extensions:
public static class LinqExtensions { public static IEnumerable<TSource> Except<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource, TSource, bool> comparer) { return first.Where(x => second.Count(y => comparer(x, y)) == 0); } public static IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource, TSource, bool> comparer) { return first.Where(x => second.Count(y => comparer(x, y)) == 1); } }
Now I can compare on the ID by supplying the comparison logic as a lambda expression:
Console.WriteLine("Except"); first.Except(second, (x, y) => x.ID == y.ID).ToList().ForEach(x => Console.WriteLine("ID=" + x.ID + ", " + x.Name)); Console.WriteLine("Intersect"); first.Intersect(second, (x, y) => x.ID == y.ID).ToList().ForEach(x => Console.WriteLine("ID=" + x.ID + ", " + x.Name));
This gives me the results I want:
Except ID=1, Alpha Intersect ID=2, Beta ID=3, Gamma
Hope you find them useful!