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?

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

То, что вам нужно, является «отличным» эффектом. Я не думаю, что это часть LINQ, поскольку она довольно проста:

 public static IEnumerable DistinctBy (this IEnumerable source, Func keySelector) { HashSet seenKeys = new HashSet(); 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 .

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

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

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

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

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

Вы также можете использовать синтаксис запроса, если хотите, чтобы он выглядел как 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(); 

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

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

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

 List pList = new List(); /* Fill list */ var result = pList.Where(p => p.Name != null).GroupBy(p => p.Id).Select(grp => grp.FirstorDefault()); 

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

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

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

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

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

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

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

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

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

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

 var people = new List(); 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 – указание свойства в отдельной функции

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

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

 using PowerfulExtensions.Linq; ... var distinct = myArray.Distinct(x => xA, x => xB); 

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

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

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

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

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

 public class LambdaEqualityComparer : IEqualityComparer { private Func _selector; public LambdaEqualityComparer(Func 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 Distinct( this IEnumerable source, Func selector) { return source.Distinct(new LambdaEqualityComparer(selector)); } 

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

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

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

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

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

 var wordComparer = KeyEqualityComparer.Null(). 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 Null() { return null; } public static IEqualityComparer EqualityComparerBy( this IEnumerable source, Func keyFunc) { return new KeyEqualityComparer(keyFunc); } public static KeyEqualityComparer ThenBy( this IEqualityComparer equalityComparer, Func keyFunc) { return new KeyEqualityComparer(keyFunc, equalityComparer); } } public struct KeyEqualityComparer: IEqualityComparer { public KeyEqualityComparer( Func keyFunc, IEqualityComparer equalityComparer = null) { KeyFunc = keyFunc; EqualityComparer = equalityComparer; } public bool Equals(T x, T y) { return ((EqualityComparer == null) || EqualityComparer.Equals(x, y)) && EqualityComparer.Default.Equals(KeyFunc(x), KeyFunc(y)); } public int GetHashCode(T obj) { var hash = EqualityComparer.Default.GetHashCode(KeyFunc(obj)); if (EqualityComparer != null) { var hash2 = EqualityComparer.GetHashCode(obj); hash ^= (hash2 << 5) + hash2; } return hash; } public readonly Func KeyFunc; public readonly IEqualityComparer EqualityComparer; } 

Более подробная информация находится на нашем сайте: IEqualityComparer в LINQ .

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

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

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

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

 public class CustomEqualityComparer : IEqualityComparer { Func _comparison; Func _hashCodeFactory; public CustomEqualityComparer(Func comparison, Func 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 areEqual = (p1, p2) => int.Equals(p1.Id, p2.Id); Func getHashCode = (p) => p.Id.GetHashCode(); var query = people.Distinct(new CustomEqualityComparer(areEqual, getHashCode)); 

Вуаля! 🙂

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

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

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

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

РЕДАКТИРОВАТЬ

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

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

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

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

Попробуйте использовать код ниже.

 var Item = GetAll().GroupBy(x => x .Id).ToList(); 
  • Сортировка списка на основе другого списка
  • Как создать дерево выражений LINQ, чтобы выбрать анонимный тип
  • Как получить индекс с помощью LINQ?
  • Как я могу отклонить все изменения в DataContext от Linq to SQL?
  • Оптимальный запрос LINQ для получения случайной подпапки - Shuffle
  • Выберите синтаксический анализ int, если строка была подвержена анализу int
  • Как получить XML-узел из XDocument
  • Какие существуют гарантии на сложности выполнения (Big-O) методов LINQ?
  • Как преобразовать дерево выражений в частичный запрос SQL?
  • LINQ Где игнорировать акцентуацию и случай
  • Перечислить сложную сортировку
  • Давайте будем гением компьютера.