LINQ's Distinct() on a particular property
我在玩linq来了解它,但是我不知道当我没有一个简单的列表时如何使用distinct(一个简单的整数列表很容易做到,这不是问题)。如果我想在对象的一个或多个属性的列表中使用distinct,该怎么办?
示例:如果对象是
1 2 3 | Person1: Id=1, Name="Test1" Person2: Id=1, Name="Test1" Person3: Id=2, Name="Test2" |
我怎样才能得到"人1"和"人3"?有可能吗?
如果使用Linq不可能,那么根据.NET 3.5中的某些属性,使用EDOCX1[0]列表的最佳方法是什么?
What if I want to obtain a distinct list based on one or more properties?
简单!你想把他们分组,从小组中选出一个赢家。
1 2 3 4 | List<Person> distinctPeople = allPeople .GroupBy(p => p.PersonId) .Select(g => g.First()) .ToList(); |
如果要在多个属性上定义组,请执行以下操作:
1 2 3 4 | List<Person> distinctPeople = allPeople .GroupBy(p => new {p.PersonId, p.FavoriteColor} ) .Select(g => g.First()) .ToList(); |
编辑:这现在是Morelinq的一部分。
你需要的是一个有效的"独特的"。我不相信它是Linq的一部分,尽管它相当容易写:
1 2 3 4 5 6 7 8 9 10 11 12 | 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; } } } |
因此,要仅使用
1 | var query = people.DistinctBy(p => p.Id); |
要使用多个属性,可以使用匿名类型,这些类型适当地实现相等:
1 |
未经测试,但它应该可以工作(现在至少可以编译)。
不过,它假定键的默认比较器-如果要传入相等比较器,只需将其传递给
如果希望查询语法看起来像LINQ,也可以使用查询语法:
1 2 3 4 | 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(); |
用途:
1 2 3 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()); |
我认为这已经足够了:
1 | list.Select(s => s.MyField).Distinct(); |
解决方案首先按字段分组,然后选择FirstOrDefault项。
1 2 3 4 | List<Person> distinctPeople = allPeople .GroupBy(p => p.PersonId) .Select(g => g.FirstOrDefault()) .ToList(); |
您可以使用标准的
1 | Persons.ToLookup(p => p.Id).Select(coll => coll.First()); |
以下代码在功能上等同于乔恩·斯基特的答案。
在.NET 4.5上测试,应该可以在Linq的任何早期版本上工作。
1 2 3 4 5 6 | 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))); } |
顺便说一下,看看乔恩·斯基特在谷歌代码上最新版本的distingby.cs。
我写了一篇文章,解释了如何扩展distinct函数,以便您可以执行以下操作:
1 2 3 4 5 6 7 8 |
这是一篇文章:扩展linq-在distinct函数中指定属性
如果需要对多个属性使用不同的方法,可以查看我的powerfulextensions库。目前它还处于一个非常年轻的阶段,但是您已经可以使用distinct、union、intersect等方法,除了在任何数量的属性上;
这就是你如何使用它:
1 2 3 | using PowerfulExtensions.Linq; ... var distinct = myArray.Distinct(x => x.A, x => x.B); |
你可以这样做(尽管不是闪电般的快):
1 | people.Where(p => !people.Any(q => (p != q && p.Id == q.Id))); |
也就是说,"选择列表中没有其他具有相同ID的人的所有人。"
注意,在您的示例中,只需选择Person3。在前两个问题中,我不知道该如何判断你想要哪一个。
当我们在项目中面对这样的任务时,我们定义了一个小的API来组成比较器。
所以,用例是这样的:
1 2 3 4 5 | var wordComparer = KeyEqualityComparer.Null<Word>(). ThenBy(item => item.Text). ThenBy(item => item.LangID); ... source.Select(...).Distinct(wordComparer); |
API本身看起来是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | 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。
我个人使用以下课程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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(); } } |
然后,扩展方法:
1 2 3 4 5 | public static IEnumerable<TSource> Distinct<TSource, TCompare>( this IEnumerable<TSource> source, Func<TSource, TCompare> selector) { return source.Distinct(new LambdaEqualityComparer<TSource, TCompare>(selector)); } |
最后,预期用途:
1 2 | var dates = new List<DateTime>() { /* ... */ } var distinctYears = dates.Distinct(date => date.Year); |
我发现使用这种方法的好处是,对于接受
如果您不想将morelinq库添加到您的项目中只是为了获得
首先创建一个通用自定义相等比较器类,该类使用lambda语法对通用类的两个实例执行自定义比较:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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); } } |
然后在您的主代码中,您可以这样使用它:
1 2 3 4 5 | 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)); |
哇!:)
上述假设如下:
- 财产
Person.Id 属于int 类。 people 集合不包含任何空元素
如果集合可以包含空值,则只需重写lambda以检查空值,例如:
1 2 3 4 | Func<Person, Person, bool> areEqual = (p1, p2) => { return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false; }; |
编辑
这种方法类似于弗拉基米尔·内斯特罗夫斯基的回答,但更简单。
它也类似于Joel的答案,但允许涉及多个属性的复杂比较逻辑。
但是,如果您的对象只能在
1 2 |
要做到这一点,最好的方法是重写equals和gethash来处理这一点(参见堆栈溢出问题,此代码返回不同的值)。但是,我希望返回一个强类型集合,而不是匿名类型),但是如果您需要在整个代码中都是通用的,那么本文中的解决方案非常好。
重写equals(object obj)和gethashcode()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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(); } } |
然后打电话给:
1 |
可以使用distinct by()通过对象属性获取不同的记录。使用前只需添加以下语句:
using Microsoft.Ajax.Utilities;
然后按如下方式使用:
1 | var listToReturn = responseList.DistinctBy(x => x.Index).ToList(); |
其中"index"是我希望在其上区分数据的属性。
您应该能够覆盖equals on person,以便在person.id上实际执行equals。这应该会导致您所追求的行为。
请尝试以下代码。
1 | var Item = GetAll().GroupBy(x => x .Id).ToList(); |