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

C#LinqDistinct

C# Problem Overview


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?

C# Solutions


Solution 1 - C#

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));

Solution 2 - C#

It's unfortunate that Distinct doesn't come up with such an overload, so what you have is a good option.

With MoreLinq, you can use the DistinctBy operator.

var distincts = bundle.GetAllThings.DistinctBy(a => a.Id); 

You might also want to consider writing a generic ProjectionEqualityComparer that can turn the appropriate delegate into an IEqualityComparer<T> implementation, such as the one listed here.

Solution 3 - C#

Here is my perverse dirty little vanilla C# trick:

entities
    .GroupBy(e => e.Id)
    .Select(g => g.First())

Solution 4 - C#

This link shows how to create the extension method to be able to use Distinct in the manner you gave. You'll need to write two Distinct extension methods, and one IEqualityComparer.

Here's the code, from the site:

public static class Extensions
    {
        public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source, Func<T, T, bool> comparer)
        {           
            return source.Distinct(new DelegateComparer<T>(comparer));
        }
     
        public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source, Func<T, T, bool> comparer, Func<T,int> hashMethod)
        {
            return source.Distinct(new DelegateComparer<T>(comparer,hashMethod));
        }
    }
     
    public class DelegateComparer<T> : IEqualityComparer<T>
    {
        private Func<T, T, bool> _equals;
        private Func<T,int> _getHashCode;
     
        public DelegateComparer(Func<T, T, bool> equals)
        {
            this._equals = equals;
        }
     
        public DelegateComparer(Func<T, T, bool> equals, Func<T,int> getHashCode)
        {
            this._equals = equals;
            this._getHashCode = getHashCode;
        }
     
        public bool Equals(T a, T b)
        {
            return _equals(a, b);
        }
     
        public int GetHashCode(T a)
        {
            if (_getHashCode != null)       
                return _getHashCode(a);       
            else
                return a.GetHashCode();
        }
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAidanView Question on Stackoverflow
Solution 1 - C#driisView Answer on Stackoverflow
Solution 2 - C#AniView Answer on Stackoverflow
Solution 3 - C#EricView Answer on Stackoverflow
Solution 4 - C#Ahmed SubhaniView Answer on Stackoverflow