Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

linq

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?

like image 719
Aidan Avatar asked Jan 05 '11 17:01

Aidan


4 Answers

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));
like image 73
driis Avatar answered Nov 18 '22 01:11

driis


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.

like image 35
Ani Avatar answered Nov 18 '22 01:11

Ani


Here is my perverse dirty little vanilla C# trick:

entities
    .GroupBy(e => e.Id)
    .Select(g => g.First())
like image 5
Eric Avatar answered Nov 18 '22 01:11

Eric


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();
        }
    }
like image 2
Ahmed Subhani Avatar answered Nov 18 '22 00:11

Ahmed Subhani