Search code examples
c#linqdistinct

Use a delegate for the equality comparer for LINQ's Distinct()


I have a LINQ Distinct() statement that uses my own custom comparer, like this:

class MyComparer<T> : IEqualityComparer<T> where T : MyType
{
    public bool Equals(T x, T y)
    {
        return x.Id.Equals(y.Id);
    }

    public int GetHashCode(T obj)
    {
        return obj.Id.GetHashCode();
    }
}

...

var distincts = bundle.GetAllThings.Distinct(new MyComparer<MySubType>());

This is all fine and dandy and works as I want. Out of curiosity, do I need to define my own Comparer, or can I replace it with a delegate? I thought I should be able to do something like this:

var distincts = bundle.GetAllThings.Distinct((a,b) => a.Id == b.Id);

But this doesn't compile. Is there a neat trick?


Solution

  • Distinct takes an IEqualityComparer as the second argument, so you will need an IEqualityComparer. It's not too hard to make a generic one that will take a delegate, though. Of course, this has probably already been implemented in some places, such as MoreLINQ suggested in one of the other answers.

    You could implement it something like this:

    public static class Compare
    {
        public static IEnumerable<T> DistinctBy<T, TIdentity>(this IEnumerable<T> source, Func<T, TIdentity> identitySelector)
        {
            return source.Distinct(Compare.By(identitySelector));
        }
    
        public static IEqualityComparer<TSource> By<TSource, TIdentity>(Func<TSource, TIdentity> identitySelector)
        {
            return new DelegateComparer<TSource, TIdentity>(identitySelector);
        }
    
        private class DelegateComparer<T, TIdentity> : IEqualityComparer<T>
        {
            private readonly Func<T, TIdentity> identitySelector;
    
            public DelegateComparer(Func<T, TIdentity> identitySelector)
            {
                this.identitySelector = identitySelector;
            }
    
            public bool Equals(T x, T y)
            {
                return Equals(identitySelector(x), identitySelector(y));
            }
    
            public int GetHashCode(T obj)
            {
                return identitySelector(obj).GetHashCode();
            }
        }
    }
    

    Which gives you the syntax:

    source.DistinctBy(a => a.Id);
    

    Or, if you feel it's clearer this way:

    source.Distinct(Compare.By(a => a.Id));