52

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Aidan
  • 4,783
  • 5
  • 34
  • 58

5 Answers5

108

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));
driis
  • 161,458
  • 45
  • 265
  • 341
  • 2
    Having done so, you can also write your own extension method that takes a delegate. – Amanda Mitchell Jan 05 '11 at 18:09
  • 3
    Cool! If someone missed it you can do: source.DistinctBy(x => new { x.A, x.B }); – watbywbarif Dec 16 '14 at 11:41
  • `TIdentity` should have a `IEquatable` constraint, and therefore, use the `identitySelector(x).Equals(identitySelector(y))` instead. This is in order to prevent a reference comparison due to generics not inferring the constructed type overloads. – Miguel A. Arilla Nov 27 '17 at 11:46
11

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.

Community
  • 1
  • 1
Ani
  • 111,048
  • 26
  • 262
  • 307
7

Here is my perverse dirty little vanilla C# trick:

entities
    .GroupBy(e => e.Id)
    .Select(g => g.First())
Eric
  • 16,397
  • 8
  • 68
  • 76
  • 2
    According to https://stackoverflow.com/a/1183877/529618 - you can actually simplify this to a single GroupBy call. It has an overload where you can provide a selector: `entities.GroupBy(e => e.Id, (id, g) => g.First())` – Alain Apr 07 '20 at 00:19
2

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();
        }
    }
Ahmed Subhani
  • 45
  • 1
  • 6
  • 5
    The problem with this code is that it does not follow the rules for GetHashCode and Equals (see msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx). Any time you use the first overload on a custom class, you are very likely to get incorrect results. GetHashCode *must* return the same value for two objects when Equals returns true. – Gideon Engelberth Jan 05 '11 at 18:40
1

As of NET6, a DistinctBy extension method was added to the library in System.Linq.

example:

Planet[] planets =
{
    Planet.Mercury,
    Planet.Venus,
    Planet.Earth,
    Planet.Mars,
    Planet.Jupiter,
    Planet.Saturn,
    Planet.Uranus,
    Planet.Neptune,
    Planet.Pluto
};

foreach (Planet planet in planets.DistinctBy(p => p.Type))
{
    Console.WriteLine(planet);
}

// This code produces the following output:
//     Planet { Name = Mercury, Type = Rock, OrderFromSun = 1 }
//     Planet { Name = Jupiter, Type = Gas, OrderFromSun = 5 }
//     Planet { Name = Uranus, Type = Liquid, OrderFromSun = 7 }
//     Planet { Name = Pluto, Type = Ice, OrderFromSun = 9 }
Marc
  • 9,254
  • 2
  • 29
  • 31