LINQ's Distinct () для определенного свойства - PullRequest
936 голосов
/ 28 января 2009

Я играю с LINQ, чтобы узнать об этом, но я не могу понять, как использовать Distinct, когда у меня нет простого списка (простой список целых чисел довольно прост, это не вопрос) , Что мне делать, если вы хотите использовать Distinct в списке объектов на одном или больше свойствах объекта?

Пример: если объект Person, со свойством Id. Как я могу получить всех людей и использовать Distinct на них со свойством Id объекта?

Person1: Id=1, Name="Test1"
Person2: Id=1, Name="Test1"
Person3: Id=2, Name="Test2"

Как я могу получить только Person1 и Person3? Это возможно?

Если это невозможно с LINQ, как лучше всего иметь список Person в зависимости от некоторых его свойств в .NET 3.5?

Ответы [ 20 ]

5 голосов
/ 28 января 2009

Вы можете сделать это (хотя и не молниеносно) примерно так:

people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));

То есть «выбрать всех людей, у которых в списке нет другого человека с таким же идентификатором».

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

4 голосов
/ 11 июля 2014

Когда мы столкнулись с такой задачей в нашем проекте, мы определили небольшой API для составления компараторов.

Итак, пример использования был таким:

var wordComparer = KeyEqualityComparer.Null<Word>().
    ThenBy(item => item.Text).
    ThenBy(item => item.LangID);
...
source.Select(...).Distinct(wordComparer);

А сам API выглядит так:

using System;
using System.Collections;
using System.Collections.Generic;

public static class KeyEqualityComparer
{
    public static IEqualityComparer<T> Null<T>()
    {
        return null;
    }

    public static IEqualityComparer<T> EqualityComparerBy<T, K>(
        this IEnumerable<T> source,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc);
    }

    public static KeyEqualityComparer<T, K> ThenBy<T, K>(
        this IEqualityComparer<T> equalityComparer,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc, equalityComparer);
    }
}

public struct KeyEqualityComparer<T, K>: IEqualityComparer<T>
{
    public KeyEqualityComparer(
        Func<T, K> keyFunc,
        IEqualityComparer<T> equalityComparer = null)
    {
        KeyFunc = keyFunc;
        EqualityComparer = equalityComparer;
    }

    public bool Equals(T x, T y)
    {
        return ((EqualityComparer == null) || EqualityComparer.Equals(x, y)) &&
                EqualityComparer<K>.Default.Equals(KeyFunc(x), KeyFunc(y));
    }

    public int GetHashCode(T obj)
    {
        var hash = EqualityComparer<K>.Default.GetHashCode(KeyFunc(obj));

        if (EqualityComparer != null)
        {
            var hash2 = EqualityComparer.GetHashCode(obj);

            hash ^= (hash2 << 5) + hash2;
        }

        return hash;
    }

    public readonly Func<T, K> KeyFunc;
    public readonly IEqualityComparer<T> EqualityComparer;
}

Подробнее на нашем сайте: IEqualityComparer в LINQ .

4 голосов
/ 30 октября 2015

Лично я использую следующий класс:

public class LambdaEqualityComparer<TSource, TDest> : 
    IEqualityComparer<TSource>
{
    private Func<TSource, TDest> _selector;

    public LambdaEqualityComparer(Func<TSource, TDest> selector)
    {
        _selector = selector;
    }

    public bool Equals(TSource obj, TSource other)
    {
        return _selector(obj).Equals(_selector(other));
    }

    public int GetHashCode(TSource obj)
    {
        return _selector(obj).GetHashCode();
    }
}

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

public static IEnumerable<TSource> Distinct<TSource, TCompare>(
    this IEnumerable<TSource> source, Func<TSource, TCompare> selector)
{
    return source.Distinct(new LambdaEqualityComparer<TSource, TCompare>(selector));
}

Наконец, предполагаемое использование:

var dates = new List<DateTime>() { /* ... */ }
var distinctYears = dates.Distinct(date => date.Year);

Преимущество, которое я нашел, используя этот подход, заключается в повторном использовании класса LambdaEqualityComparer для других методов, которые принимают IEqualityComparer. (О, и я оставляю материал yield исходной реализации LINQ ...)

3 голосов
/ 22 августа 2016

Если вы не хотите добавлять библиотеку MoreLinq в свой проект просто для того, чтобы получить функциональность DistinctBy, то вы можете получить тот же конечный результат, используя перегрузку метода Distinct Linq, которая принимает аргумент IEqualityComparer .

Вы начинаете с создания универсального пользовательского класса сравнения равенств, который использует лямбда-синтаксис для выполнения пользовательского сравнения двух экземпляров универсального класса:

public class CustomEqualityComparer<T> : IEqualityComparer<T>
{
    Func<T, T, bool> _comparison;
    Func<T, int> _hashCodeFactory;

    public CustomEqualityComparer(Func<T, T, bool> comparison, Func<T, int> hashCodeFactory)
    {
        _comparison = comparison;
        _hashCodeFactory = hashCodeFactory;
    }

    public bool Equals(T x, T y)
    {
        return _comparison(x, y);
    }

    public int GetHashCode(T obj)
    {
        return _hashCodeFactory(obj);
    }
}

Тогда в вашем основном коде вы используете его так:

Func<Person, Person, bool> areEqual = (p1, p2) => int.Equals(p1.Id, p2.Id);

Func<Person, int> getHashCode = (p) => p.Id.GetHashCode();

var query = people.Distinct(new CustomEqualityComparer<Person>(areEqual, getHashCode));

Вуаля! :)

Вышесказанное предполагает следующее:

  • Свойство Person.Id относится к типу int
  • Коллекция people не содержит нулевых элементов

Если коллекция может содержать нули, просто перепишите лямбда-выражения для проверки на ноль, например ::1010

Func<Person, Person, bool> areEqual = (p1, p2) => 
{
    return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false;
};

EDIT

Этот подход похож на тот, что был в ответе Владимира Нестеровского, но проще.

Он также аналогичен ответу Джоэла, но допускает сложную логику сравнения, включающую несколько свойств.

Однако, если ваши объекты могут отличаться только на Id, тогда другой пользователь дал правильный ответ, что все, что вам нужно сделать, это переопределить реализации по умолчанию GetHashCode() и Equals() в вашем классе Person, а затем просто используйте стандартный метод Distinct() Linq для фильтрации любых дубликатов.

2 голосов
/ 27 сентября 2018

Переопределить Равно (объект obj) и GetHashCode () методы:

class Person
{
    public int Id { get; set; }
    public int Name { get; set; }

    public override bool Equals(object obj)
    {
        return ((Person)obj).Id == Id;
        // or: 
        // var o = (Person)obj;
        // return o.Id == Id && o.Name == Name;
    }
    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

, а затем просто позвоните:

List<Person> distinctList = new[] { person1, person2, person3 }.Distinct().ToList();
2 голосов
/ 21 октября 2013

Лучший способ сделать это, совместимый с другими версиями .NET, - переопределить Equals и GetHash, чтобы справиться с этим (см. Вопрос переполнения стека . Этот код возвращает различные значения. Однако я хочу, чтобы возвращать строго типизированную коллекцию в противоположность анонимному типу ), но если вам нужно что-то общее в вашем коде, решения в этой статье хороши.

2 голосов
/ 16 мая 2016
List<Person>lst=new List<Person>
        var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();
0 голосов
/ 27 марта 2019

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

с использованием Microsoft.Ajax.Utilities;

, а затем используйте его следующим образом:

var listToReturn = responseList.DistinctBy(x => x.Index).ToList();

где 'Index' - это свойство, для которого я хочу, чтобы данные были различны.

0 голосов
/ 16 июля 2018

Пожалуйста, попробуйте с кодом ниже.

var Item = GetAll().GroupBy(x => x .Id).ToList();
0 голосов
/ 28 января 2009

Вы должны иметь возможность переопределить Equals для человека, чтобы фактически сделать Equals для Person.id. Это должно привести к поведению, за которым вы следите.

...