16

Could someone provide me a small example on how to Use the .NET 6 LINQ IntersectBy and ExceptBy methods? MSDN hasn't got any examples and the one I tried doesn't compile due to CS0411 error. The example I tried:

namespace Test
{
    internal struct Example
    {
        public int X { get; set; }
        public int Y { get; set; }

        public override string ToString()
        {
            return $"{X}, {Y}";
        }
    }

    public class Program
    {
        public static void Main()
        {
            var elements = new List<Example>
            {
                new Example { X = 10, Y = 20 },
                new Example { X = 11, Y = 23 },
            };

            var elements2 = new List<Example>
            {
                new Example { X = 10, Y = 12 },
                new Example { X = 44, Y = 20 },
            };


            //ok
            var union = elements.UnionBy(elements2, x => x.X);
            
            //CS0411 - Why ?
            var intersect = elements.IntersectBy(elements2, x => x.X);

            //CS0411 - Why ?
            var except = elements.ExceptBy(elements2, x => x.X);

            Console.ReadKey();
        }
    }
}
webmaster442
  • 231
  • 1
  • 3
  • 9

3 Answers3

22

Granted the documentation doesn't have any examples, it states that the selector function should select TKey i.e. the type of the second collection. The following should work:

var intersect = elements.IntersectBy(elements2, x => x);
var except = elements.ExceptBy(elements2, x => x);

Although I think this may be closer to what you want:

var intersect = elements.IntersectBy(elements2.Select(e => e.X), x => x.X);

For more complex types, you may want to consider implementing an IEqualityComparer and using the overloads that take one as an argument.

HasaniH
  • 8,232
  • 6
  • 41
  • 59
  • 3
    I wonder why they choose to use a different pattern with `UnionBy` vs `IntersectBy` and `ExceptBy`. – Bradley Uffner Nov 27 '21 at 14:43
  • 1
    Exactly, That got me confused as well. – webmaster442 Nov 27 '21 at 14:44
  • 1
    The comments on the functions read the same, that the 2nd argument should be a "Key Selector", this almost seems like someone made a mistake somewhere in either the documentation or the framework code. The pattern in `UnionBy` seems to be what I would expect the others to look like as well. – Bradley Uffner Nov 27 '21 at 14:46
  • The args in UnionBy are different though (IEnumerable, IEnumerable, Func). Not saying it isn't confusing but I think the distinction is that Union requires items from both collections to produce the result while Intersect and Except only require items in the source collection and the comparison key. – HasaniH Nov 27 '21 at 14:50
  • 2
    @BradleyUffner The answer to that is IMO because `UnionBy` needs to return a single list of joined results, so they need to be the same type. Whereas `IntersectBy` and `ExceptBy` only return results from the source collection, so with these parameters you can use an `IEnumerable`, and if you need to select from a different collection just use `Select` (which wouldn't really make sense for `UnionBy`) – Charlieface Nov 27 '21 at 21:18
4

I made my own ExceptByProperty method like this

Usage:

var new = items.ExceptByProperty(duplicateItems, x => x.Id).ToList();

Code:

public static class EnumerableExtensions
{
    public static IEnumerable<TSource> ExceptByProperty<TSource, TProperty>(this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource, TProperty> keySelector)
    {
        return first.ExceptBy(second, x => x, GenericComparer<TSource, TProperty>.Comparer(keySelector));
    }

}

public sealed class GenericComparer<T, TProperty> : IEqualityComparer<T>
{
    public static IEqualityComparer<T> Comparer(Func<T, TProperty> selector)
    {
        return new GenericComparer<T, TProperty>(selector);
    }

    private readonly Func<T, TProperty> selector;

    public GenericComparer(Func<T, TProperty> selector)
    {
        this.selector = selector;
    }

    public bool Equals(T? x, T? y)
    {
        if (x == null || y == null) return false;

        return Equals(selector(x), selector(y));
    }

    public int GetHashCode([DisallowNull] T obj)
    {
        object? value = selector(obj);

        if (value == null) return obj.GetHashCode();

        return value.GetHashCode();
    }
}
Michael
  • 3,350
  • 2
  • 21
  • 35
1

Here are a couple of additional extension methods that more closely resemble the built-in version of UnionBy. Should make things a bit more consistent.

using System.Linq;

public static class MyExtensions 
{
    /// <inheritdoc cref="Enumerable.ExceptBy{TSource, TKey}(IEnumerable{TSource}, IEnumerable{TKey}, Func{TSource, TKey}, IEqualityComparer{TKey}?)"/>
    public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource, TKey> keySelector)
    {
        return first.ExceptBy(second.Select(keySelector), keySelector);
    }

    /// <inheritdoc cref="Enumerable.IntersectBy{TSource, TKey}(IEnumerable{TSource}, IEnumerable{TKey}, Func{TSource, TKey}, IEqualityComparer{TKey}?)"/>
    public static IEnumerable<TSource> IntersectBy<TSource, TKey>(this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource, TKey> keySelector)
    {
         return first.IntersectBy(second.Select(keySelector), keySelector);
    }
}
The Thirsty Ape
  • 983
  • 3
  • 16
  • 31