Using LINQ to objects Intersect and Except on a specific property

Diana picture Diana · Oct 22, 2010 · Viewed 7.3k times · Source

When I have 2 List<string> objects, then I can use Intersect and Except on them directly to get an output IEnumerable<string>. That's simple enough, but what if I want the intersection/disjuction on something more complex?

Example, trying to get a collection of ClassA objects which is the result of the intersect on ClassA object's AStr1 and ClassB object's BStr; :

public class ClassA {
    public string AStr1 { get; set; }
    public string AStr2 { get; set; }
    public int AInt { get; set; }
}
public class ClassB {
    public string BStr { get; set; }
    public int BInt { get; set; }
}
public class Whatever {
    public void xyz(List<ClassA> aObj, List<ClassB> bObj) {
        // *** this line is horribly incorrect ***
        IEnumberable<ClassA> result =
            aObj.Intersect(bObj).Where(a, b => a.AStr1 == b.BStr);
    }
}

How can I fix the noted line to achieve this intersection.

Answer

Jon Skeet picture Jon Skeet · Oct 22, 2010

MoreLINQ has ExceptBy. It doesn't have IntersectBy yet, but you could easily write your own implementation, and possibly even contribute it to MoreLINQ afterwards :)

It would probably look something like this (omitting error checking):

public static IEnumerable<TSource> IntersectBy<TSource, TKey>(
    this IEnumerable<TSource> first,
    IEnumerable<TSource> second,
    Func<TSource, TKey> keySelector,
    IEqualityComparer<TKey> keyComparer)
{
    HashSet<TKey> keys = new HashSet<TKey>(first.Select(keySelector),
                                           keyComparer);
    foreach (var element in second)
    {
        TKey key = keySelector(element);
        // Remove the key so we only yield once
        if (keys.Remove(key))
        {
            yield return element;
        }
    }
}

If you wanted to perform an intersection on two completely different types which happened to have a common property type, you could make a more general method with three type parameters (one for first, one for second, and one for the common key type).