Distinct () LINQ для определенного свойства
Я играю с 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?
19 ответов
редактировать: теперь это часть 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
конструктор.
Что делать, если я хочу получить отдельный список на основе один или больше свойства?
просто! Вы хотите сгруппировать их и выбрать победителя из группы.
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();
вы также можете использовать синтаксис запроса, если хотите, чтобы он выглядел как 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();
использование:
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
выполнить определенную функцию.
Вы можете сделать это со стандартным Linq.ToLookup()
. Это создаст коллекцию значений для каждого уникального ключа. Просто выберите первый элемент в коллекции
Persons.ToLookup(p => p.Id).Select(coll => coll.First());
решение первая группа по полям, затем выберите элемент firstordefault.
List<Person> distinctPeople = allPeople
.GroupBy(p => p.PersonId)
.Select(g => g.FirstOrDefault())
.ToList();
следующий код функционально эквивалентен ответ Джона Скита.
протестировано на .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)));
}
Incidentially, проверить последняя версия DistinctBy Джона Скита.cs на Google Code.
Я написал статью, в которой объясняется, как расширить функцию 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-указание свойства в отдельной функции
Если вам нужен отдельный метод для нескольких свойств, вы можете проверить my PowerfulExtensions библиотека. В настоящее время он находится в очень молодой стадии, но уже вы можете использовать такие методы, как Distinct, Union, Intersect, за исключением любого количества свойств;
вот как вы его используете:
using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);
вы можете сделать это (хотя и не молниеносно) так:
people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));
то есть, " выберите всех людей, где нет другого другого человека в списке с тем же идентификатором."
имейте в виду, в вашем примере, что просто выберите человека 3. Я не уверен, как сказать, что вы хотите, из двух предыдущих.
лично я использую следующий класс:
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...)
когда мы столкнулись с такой задачей в нашем проекте, мы определили небольшой 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.
лучший способ сделать это, который будет совместим с другими версиями .NET, - переопределить Equals и GetHash для обработки этого (см. вопрос переполнения стека этот код возвращает различные значения. Однако я хочу вернуть строго типизированную коллекцию, а не анонимный тип), но если вам нужно что-то общее во всем вашем коде, решения в этой статье великолепны.
List<Person>lst=new List<Person>
var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();
если вы не хотите добавлять библиотеку MoreLinq в свой проект, чтобы получить DistinctBy
функциональности, то вы можете получить тот же конечный результат, используя перегрузку LINQ на это Distinct
метод, который принимает в качестве
вы должны иметь возможность переопределять Equals на person, чтобы фактически делать Equals на Person.id. Это должно привести к поведению, которое вам нужно.
переопределить 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();