LINQ Distinct() для определенного свойства

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

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

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

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

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

Ответ 1

EDIT: теперь это часть MoreLINQ.

Что вам нужно, это "отлично". Я не верю, что это часть LINQ, поскольку она довольно проста в написании:

public static IEnumerable<TSource> DistinctBy<TSource, TKey>
    (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> seenKeys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        if (seenKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}

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

var query = people.DistinctBy(p => p.Id);

И для использования нескольких свойств вы можете использовать анонимные типы, которые соответствующим образом реализуют равенство:

var query = people.DistinctBy(p => new { p.Id, p.Name });

Неподтвержденный, но он должен работать (и теперь он как минимум компилируется).

Он предполагает сопоставление по умолчанию для ключей, хотя - если вы хотите пройти в сопоставлении равенства, просто передайте его конструктору HashSet.

Ответ 2

Что делать, если я хочу получить отдельный список, основанный на одном или нескольких свойствах?

Simple! Вы хотите сгруппировать их и выбрать победителя из группы.

List<Person> distinctPeople = allPeople
  .GroupBy(p => p.PersonId)
  .Select(g => g.First())
  .ToList();

Если вы хотите определить группы по нескольким свойствам, вот как:

List<Person> distinctPeople = allPeople
  .GroupBy(p => new {p.PersonId, p.FavoriteColor} )
  .Select(g => g.First())
  .ToList();

Ответ 3

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

var uniquePeople = from p in people
                   group p by new {p.ID} //or group by new {p.ID, p.Name, p.Whatever}
                   into mygroup
                   select mygroup.FirstOrDefault();

Ответ 4

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

List<Person> pList = new List<Person>();
/* Fill list */

var result = pList.Where(p => p.Name != null).GroupBy(p => p.Id).Select(grp => grp.FirstOrDefault());

where помогает вам фильтровать записи (может быть более сложным), а также groupby и select выполнять различные функции.

Ответ 5

Я думаю, этого достаточно:

list.Select(s => s.MyField).Distinct();

Ответ 6

Сначала выберите первую группу по вашим полям, затем выберите элемент firstordefault.

    List<Person> distinctPeople = allPeople
   .GroupBy(p => p.PersonId)
   .Select(g => g.FirstOrDefault())
   .ToList();

Ответ 7

Вы можете сделать это со стандартным Linq.ToLookup(). Это создаст набор значений для каждого уникального ключа. Просто выберите первый элемент в коллекции

Persons.ToLookup(p => p.Id).Select(coll => coll.First());

Ответ 8

Следующий код функционально эквивалентен ответ Jon Skeet.

Протестировано на .NET 4.5, должно работать с любой более ранней версией LINQ.

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
  this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
  HashSet<TKey> seenKeys = new HashSet<TKey>();
  return source.Where(element => seenKeys.Add(keySelector(element)));
}

Кстати, проверьте Jon Skeet последняя версия DistinctBy.cs в Google Code.

Ответ 9

Я написал статью, в которой объясняется, как расширить функцию Distinct, чтобы вы могли сделать следующее:

var people = new List<Person>();

people.Add(new Person(1, "a", "b"));
people.Add(new Person(2, "c", "d"));
people.Add(new Person(1, "a", "b"));

foreach (var person in people.Distinct(p => p.ID))
    // Do stuff with unique list here.

Вот статья: Расширение LINQ - Указание свойства в отдельной функции

Ответ 10

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

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

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

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

Ответ 11

Если вам нужен метод Distinct для нескольких свойств, вы можете проверить мою библиотеку PowerfulExtensions. В настоящее время он находится на очень молодой стадии, но уже вы можете использовать такие методы, как Distinct, Union, Intersect, за исключением любого количества свойств;

Вот как вы его используете:

using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);

Ответ 12

Когда мы столкнулись с такой задачей в нашем проекте, мы определили небольшой 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.

Ответ 13

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

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...)

Ответ 14

Если вы не хотите добавлять библиотеку MoreLinq в свой проект, чтобы получить функциональность DistinctBy, вы можете получить тот же конечный результат, используя перегрузку метода Linq Distinct, который принимает аргумент 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));

Voila!:)

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

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

Если коллекция может содержать нули, просто перепишите lambdas для проверки нулевого значения, например:

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

ИЗМЕНИТЬ

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

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

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

Ответ 15

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

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

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

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

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

Ответ 16

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

Ответ 17

List<Person>lst=new List<Person>
        var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();

Ответ 18

Переопределите методы Equals (object 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();

Ответ 19

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

Ответ 20

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

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

var wordComparer = KeyEqualityComparer.Null<Word>().
    ThenBy(item => item.Text).
    ThenBy(item => item.LangID);
...
source.Select(...).Distinct(wordComparer);
And API itself looks like this:

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;
}

Ответ 21

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

var Item = GetAll().GroupBy(x => x .Id).ToList();