Используйте делегат для сопоставления равенства для LINQ Distinct()
У меня есть оператор LINQ Distinct(), который использует мой собственный пользовательский сопоставитель, например:
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>());
Это все прекрасно и денди и работает так, как я хочу. Из любопытства мне нужно определить свой собственный Comparer, или я могу заменить его делегатом? Я думал, что смогу сделать что-то вроде этого:
var distincts = bundle.GetAllThings.Distinct((a,b) => a.Id == b.Id);
Но это не компилируется. Есть ли опрятный трюк?
Ответы
Ответ 1
Distinct принимает IEqualityComparer как второй аргумент, поэтому вам понадобится IEqualityComparer. Однако не сложно сделать общий, который возьмет делегата. Конечно, это, вероятно, уже реализовано в некоторых местах, например, MoreLINQ, предложенное в одном из других ответов.
Вы можете реализовать его примерно так:
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();
}
}
}
Что дает вам синтаксис:
source.DistinctBy(a => a.Id);
Или, если вы считаете это более ясным:
source.Distinct(Compare.By(a => a.Id));
Ответ 2
Несчастливо, что Distinct
не вызывает такую перегрузку, поэтому у вас есть хороший вариант.
С MoreLinq вы можете использовать DistinctBy
.
var distincts = bundle.GetAllThings.DistinctBy(a => a.Id);
Возможно, вам захочется также написать общий ProjectionEqualityComparer
, который может превратить соответствующий делегат в реализацию IEqualityComparer<T>
, например, указанную здесь.
Ответ 3
Эта ссылка показывает, как создать метод расширения, чтобы иметь возможность использовать Distinct в том виде, который вы дали. Вам нужно написать два метода расширения Distinct
и один IEqualityComparer
.
Здесь код, с сайта:
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();
}
}