Отличаться () с лямбда? - PullRequest
       31

Отличаться () с лямбда?

692 голосов
/ 19 августа 2009

Верно, поэтому у меня есть перечислимое и я хочу получить из него различные значения.

Используя System.Linq, есть, конечно, метод расширения, называемый Distinct. В простом случае его можно использовать без параметров, например:

var distinctValues = myStringList.Distinct();

Хорошо, но если у меня есть множество объектов, для которых мне нужно указать равенство, единственная доступная перегрузка:

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

Аргумент сравнения равенства должен быть экземпляром IEqualityComparer<T>. Я могу сделать это, конечно, но это несколько многословно и, ну, в общем, грязно.

То, что я ожидал, это перегрузка, которая будет принимать лямбду, скажем, Func :

var distinctValues
    = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);

Кто-нибудь знает, существует ли какое-то такое расширение или какой-то эквивалентный обходной путь? Или я что-то упустил?

В качестве альтернативы, есть ли способ указания встроенного IEqualityComparer (смущать меня)?

Обновление

Я нашел ответ Андерса Хейлсберга на сообщение на форуме MSDN на эту тему. Он говорит:

Проблема, с которой вы столкнетесь, состоит в том, что при сравнении двух объектов равные они должны иметь одинаковое возвращаемое значение GetHashCode (или внутренняя хеш-таблица, используемая Distinct, не будет работать правильно). Мы используем IEqualityComparer, потому что он совместим с пакетами реализации Equals и GetHashCode в едином интерфейсе.

Полагаю, это имеет смысл ..

Ответы [ 18 ]

957 голосов
/ 11 ноября 2010
IEnumerable<Customer> filteredList = originalList
  .GroupBy(customer => customer.CustomerId)
  .Select(group => group.First());
460 голосов
/ 19 августа 2009

Мне кажется, что ты хочешь DistinctBy от MoreLINQ . Затем вы можете написать:

var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);

Вот урезанная версия DistinctBy (без проверки недействительности и без возможности указать свой собственный ключ сравнения):

public static IEnumerable<TSource> DistinctBy<TSource, TKey>
     (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> knownKeys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        if (knownKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}
29 голосов
/ 09 декабря 2015

Чтобы обернуть вещи . Я думаю, что большинство людей, которые пришли сюда, как я, хотят самое простое из возможных решение без использования каких-либо библиотек и с максимально возможной производительностью .

(Приемлемая группа по методу для меня, я думаю, является излишним с точки зрения производительности.)

Вот простой метод расширения, использующий интерфейс IEqualityComparer , который также работает для нулевых значений.

Использование:

var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();

Код метода расширения

public static class LinqExtensions
{
    public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property)
    {
        GeneralPropertyComparer<T, TKey> comparer = new GeneralPropertyComparer<T,TKey>(property);
        return items.Distinct(comparer);
    }   
}
public class GeneralPropertyComparer<T,TKey> : IEqualityComparer<T>
{
    private Func<T, TKey> expr { get; set; }
    public GeneralPropertyComparer (Func<T, TKey> expr)
    {
        this.expr = expr;
    }
    public bool Equals(T left, T right)
    {
        var leftProp = expr.Invoke(left);
        var rightProp = expr.Invoke(right);
        if (leftProp == null && rightProp == null)
            return true;
        else if (leftProp == null ^ rightProp == null)
            return false;
        else
            return leftProp.Equals(rightProp);
    }
    public int GetHashCode(T obj)
    {
        var prop = expr.Invoke(obj);
        return (prop==null)? 0:prop.GetHashCode();
    }
}
19 голосов
/ 19 августа 2009

Нет, для этого не существует такой перегрузки метода расширения. Я находил это разочаровывающим в прошлом и поэтому обычно пишу вспомогательный класс для решения этой проблемы. Цель состоит в том, чтобы преобразовать Func<T,T,bool> в IEqualityComparer<T,T>.

Пример

public class EqualityFactory {
  private sealed class Impl<T> : IEqualityComparer<T,T> {
    private Func<T,T,bool> m_del;
    private IEqualityComparer<T> m_comp;
    public Impl(Func<T,T,bool> del) { 
      m_del = del;
      m_comp = EqualityComparer<T>.Default;
    }
    public bool Equals(T left, T right) {
      return m_del(left, right);
    } 
    public int GetHashCode(T value) {
      return m_comp.GetHashCode(value);
    }
  }
  public static IEqualityComparer<T,T> Create<T>(Func<T,T,bool> del) {
    return new Impl<T>(del);
  }
}

Это позволяет написать следующее

var distinctValues = myCustomerList
  .Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));
16 голосов
/ 20 августа 2014

Сокращенное решение

myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());
12 голосов
/ 06 октября 2010

Это будет делать то, что вы хотите, но я не знаю о производительности:

var distinctValues =
    from cust in myCustomerList
    group cust by cust.CustomerId
    into gcust
    select gcust.First();

По крайней мере, это не многословно.

10 голосов
/ 02 марта 2012

Вот простой метод расширения, который делает то, что мне нужно ...

public static class EnumerableExtensions
{
    public static IEnumerable<TKey> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
    {
        return source.GroupBy(selector).Select(x => x.Key);
    }
}

Жаль, что они не внедрили какой-то особый метод, подобный этому, в фреймворк, но эй, хо.

4 голосов
/ 19 августа 2009

Что-то, что я использовал, и это хорошо для меня.

/// <summary>
/// A class to wrap the IEqualityComparer interface into matching functions for simple implementation
/// </summary>
/// <typeparam name="T">The type of object to be compared</typeparam>
public class MyIEqualityComparer<T> : IEqualityComparer<T>
{
    /// <summary>
    /// Create a new comparer based on the given Equals and GetHashCode methods
    /// </summary>
    /// <param name="equals">The method to compute equals of two T instances</param>
    /// <param name="getHashCode">The method to compute a hashcode for a T instance</param>
    public MyIEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
    {
        if (equals == null)
            throw new ArgumentNullException("equals", "Equals parameter is required for all MyIEqualityComparer instances");
        EqualsMethod = equals;
        GetHashCodeMethod = getHashCode;
    }
    /// <summary>
    /// Gets the method used to compute equals
    /// </summary>
    public Func<T, T, bool> EqualsMethod { get; private set; }
    /// <summary>
    /// Gets the method used to compute a hash code
    /// </summary>
    public Func<T, int> GetHashCodeMethod { get; private set; }

    bool IEqualityComparer<T>.Equals(T x, T y)
    {
        return EqualsMethod(x, y);
    }

    int IEqualityComparer<T>.GetHashCode(T obj)
    {
        if (GetHashCodeMethod == null)
            return obj.GetHashCode();
        return GetHashCodeMethod(obj);
    }
}
3 голосов
/ 12 июня 2017

Возьми другой путь:

var distinctValues = myCustomerList.
Select(x => x._myCaustomerProperty).Distinct();

Последовательность, возвращающая различные элементы, сравнивает их по свойству _myCaustomerProperty.

3 голосов
/ 14 ноября 2012

Все решения, которые я видел здесь, основаны на выборе уже сопоставимого поля. Однако, если нужно сравнить по-другому, это решение здесь , кажется, работает в общем, для чего-то вроде:

somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()
...