.NET

Improving LINQ Except and Intersect by using a lambda expression for custom comparison

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!

Standard

2 thoughts on “Improving LINQ Except and Intersect by using a lambda expression for custom comparison

  1. s4ll4c says:

    Thanks for your Extensions. There’s one bug:

    quote:
    “return first.Where(x => second.Count(y => comparer(x, y)) == 1);”
    should be
    “return first.Where(x => second.Count(y => comparer(x, y)) >= 1);”

    Otherwise elements are not found if an element in list a matches to two or more elements in list b.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s