LINQ在特定属性上的Distinct()

我正在玩LINQ来了解它,但我不知道如何使用Distinct,当我没有一个简单的列表(一个简单的列表整数很容易做,这不是问题)。 我如果想在对象的一个多个属性的列表上使用Distinct ?

例如:如果一个对象是Person ,带有Property Id 。 我怎样才能得到所有的人,并使用对象的属性Id使用Distinct

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

我怎样才能得到Person1和Person3? 那可能吗?

如果LINQ不可能,根据.NET 3.5中的一些属性,最好的办法是拥有一个Person列表?

编辑 :这现在是MoreLINQ的一部分。

你需要的是一个“独特的”有效的。 我不相信它是LINQ的一部分,尽pipe写起来很简单:

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

而要使用多个属性,可以使用匿名types,它们实现相等的适当性:

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

未经testing,但它应该工作(现在至less编译)。

它假定键的默认比较器 – 如果你想传入一个相等的比较器,只要把它传递给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.Select(s => s.MyField).Distinct(); 

使用:

 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可以帮助你筛选条目(可能会更复杂)和groupbyselect执行不同的function。

下面的代码在function上等同于Jon Skeet的答案 。

在.NET 4.5上testing,应该可以在任何早期版本的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))); } 

在Google Code中 ,可以查看Jon Skeet的DistinctBy.cs的最新版本 。

你可以用标准的Linq.ToLookup()来做到这一点。 这将为每个唯一键创build一个值的集合。 只需select集合中的第一个项目

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

我写了一篇文章,解释如何扩展Distinctfunction,以便您可以执行如下操作:

 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 – 在不同的function中指定一个属性

先解决你的领域,然后selectfirstordefault项目。

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

如果你需要多个属性的独特方法,你可以看看我的PowerfulExtensions库。 目前处于一个非常年轻的阶段,但已经可以使用像Distinct,Union,Intersect等方法,除了任何数量的属性,

这是你如何使用它:

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

你可以这样做(尽pipe不是很快):

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

也就是说,“select所有在列表中没有另一个具有相同ID的人的人”。

在你的例子中,请注意,只select人员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); 

我发现使用这种方法的优点是对接受IEqualityComparer其他方法重新使用LambdaEqualityComparer类。 (哦,我把产品的东西留给原来的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; } 

更多细节在我们的网站: 在LINQ的IEqualityComparer

与其他.NET版本兼容的最好方法是覆盖Equals和GetHash来处理这个问题(请参阅堆栈溢出问题此代码返回不同的值。但是,我想要的是返回一个强types的集合,而不是一个匿名types ),但是如果你需要在整个代码中通用的东西,这篇文章中的解决scheme非常棒。

 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库添加到您的项目中以获得DistinctByfunction,那么您可以使用Linq的Distinct方法的重载获取相同的最终结果,该方法接受一个IEqualityComparer参数。

首先创build一个通用自定义相等比较器类,该类使用lambda语法来执行generics类的两个实例的自定义比较:

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

瞧! 🙂

以上假设如下:

  • Property Person.Idinttypes的
  • people集合不包含任何空元素

如果集合可能包含空值,那么只需重写lambda来检查null,例如:

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

编辑

这种方法类似于弗拉基米尔·涅斯捷罗夫斯基的答案,但更简单。

它也类似于乔尔的答案,但允许复杂的比较逻辑涉及多个属性。

然而,如果你的对象只能通过Id区别,那么另一个用户给出了正确的答案,你需要做的就是覆盖Person类中的GetHashCode()Equals()的默认实现,然后使用out-of- Linq的Distinct()方法来筛选出任何重复项。

您应该能够覆盖Equals on Person,实际上在Person.id上执行Equals。 这应该导致你以后的行为。