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 ]

1644 голосов
/ 29 января 2009

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

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();
1083 голосов
/ 29 января 2009

РЕДАКТИРОВАТЬ : теперь это часть 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.

71 голосов
/ 06 марта 2012

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

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();
60 голосов
/ 14 февраля 2012

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

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 выполняют отдельную функцию.

55 голосов
/ 23 января 2015

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

list.Select(s => s.MyField).Distinct();
32 голосов
/ 13 июля 2017

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

    List<Person> distinctPeople = allPeople
   .GroupBy(p => p.PersonId)
   .Select(g => g.FirstOrDefault())
   .ToList();
24 голосов
/ 20 января 2015

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

Persons.ToLookup(p => p.Id).Select(coll => coll.First());
16 голосов
/ 06 февраля 2013

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

Протестировано на .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)));
}

Кстати, посмотрите последнюю версию DistinctBy.cs Джона Скита в Google Code .

11 голосов
/ 11 марта 2009

Я написал статью, в которой объясняется, как расширить функцию 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 - указание свойства в отдельной функции

5 голосов
/ 16 августа 2013

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

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

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