特定属性上的LINQ's Dial的()

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

示例:如果一个对象是Person,具有属性Id。如何获取所有Person并使用Distinct和对象的属性Id

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

我怎么能只得到Person1Person3?这可能吗?

如果使用LINQ不可能,那么根据. NET 3.5中的一些属性来获得Person列表的最佳方法是什么?

1209442 次浏览

你可以这样做(虽然不是闪电般的快):

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

也就是说,“选择列表中没有其他具有相同ID的人的所有人。

请注意,在您的示例中,这只会选择人员3。我不知道如何判断您想要哪个,而不是前两个。

你应该能够在人身上覆盖Equals,以便在Person.id.上实际执行Equals,这应该会导致你所追求的行为。

编辑:这是更多LINQ的一部分。

你需要的是一个有效的“独特的”。我不相信它是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();

注意:某些查询提供程序无法解决每个组必须至少有一个元素的问题,并且First是在这种情况下调用的适当方法。如果您发现自己使用这样的查询提供程序,FirstOrDefault可能会帮助您通过查询提供程序获取查询。

注意2:对于EF Core(EF Core 6之前)兼容方法,请考虑此答案。https://stackoverflow.com/a/66529949/8155

我已经写了一篇文章,解释了如何扩展区分函数,以便您可以执行以下操作:

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-指定一个属性的区别函数

用途:

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执行不同的功能。

如果您希望它看起来像LINQ,您也可以使用查询语法:

var uniquePeople = from p in peoplegroup p by new {p.ID} //or group by new {p.ID, p.Name, p.Whatever}into mygroupselect mygroup.FirstOrDefault();

以下代码在功能上等同于Jon Skeet的回答

在. 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)));}

顺便说一下,检查Jon Skeet在Google Code上DistinctBy.cs的最新版本

更新时间2022-04-03

根据Andrew McClement的评论,最好接受John Skeet的回答。

如果你需要在多个属性上使用不同的方法,你可以看看我的功能强大库。目前它还处于一个非常年轻的阶段,但你已经可以使用不同的方法,联合,相交,除了任意数量的属性;

这就是你如何使用它:

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

与其他. NET版本兼容的最佳方法是覆盖Equals和GetHash来处理此问题(请参阅Stack Overflow问题这段代码返回不同的值。但是,我想要的是返回强类型的集合,而不是匿名类型),但如果您需要在整个代码中通用的东西,本文中的解决方案很棒。

当我们在项目中遇到这样的任务时,我们定义了一个小型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中的IEqualityComp的比较

您可以使用标准#0执行此操作。这将为每个唯一键创建一个值集合。只需选择集合中的第一项

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

我认为这就足够了:

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

我个人使用以下类:

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实现…)

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

如果您不想仅仅为了获得DistinctBy功能而将MoreLinq库添加到您的项目中,那么您可以使用接受IEqualityComparer参数的LinqDistinct方法的重载来获得相同的最终结果。

首先创建一个泛型自定义相等比较器类,该类使用lambda语法对泛型类的两个实例执行自定义比较:

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

瞧!:)

上述假设如下:

  • 属性Person.Id是类型int
  • people集合不包含任何null元素

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

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

编辑

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

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

但是,如果您的对象只能相差Id,那么另一个用户给出了正确的答案,您需要做的就是覆盖Person类中GetHashCode()Equals()的默认实现,然后只需使用Linq的开箱即用Distinct()方法来过滤掉任何重复项。

解决方案首先按您的字段分组,然后选择FirstOrDefault项。

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

请尝试使用下面的代码。

var Item = GetAll().GroupBy(x => x .Id).ToList();

覆盖Equals(对象obj)获取HashCode()方法名方法:

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

你可以使用DialinctBy()通过对象属性获取不同的记录。只需在使用它之前添加以下语句:

使用Microsoft. Ajax.实用程序;

然后像下面这样使用它:

var listToReturn = responseList.DistinctBy(x => x.Index).ToList();

其中“Index”是我希望数据与众不同的属性。

从. NET 6开始,有一个使用Linq中的新#0扩展的新解决方案,所以我们可以这样做:

var distinctPersonsById = personList.DistinctBy(x => x.Id);

#0方法的签名:

// Returns distinct elements from a sequence according to a specified// key selector function.public static IEnumerable<TSource> DistinctBy<TSource, TKey> (this IEnumerable<TSource> source,Func<TSource, TKey> keySelector);

如果您使用旧的。NET版本,其中扩展方法不是内置的,那么您可以定义自己的扩展方法:

public static class EnumerableExtensions{public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> enumerable, Func<T, TKey> keySelector){return enumerable.GroupBy(keySelector).Select(grp => grp.First());}}

使用示例:

var personsDist = persons.DistinctBy(item => item.Name);

也许这会有所帮助,试试这个。使用HashSet更具代码性能。

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector){var known = new HashSet<TKey>();return source.Where(element => known.Add(keySelector(element)));}