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 1:
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:
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:
Here is my perverse dirty little vanilla C# trick:
entities
.GroupBy(e => e.Id)
.Select(g => g.First())
Solution 4:
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();
}
}