我正在玩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无法实现,那么根据.NET 3.5中的某些属性获得Person
列表的最佳方法是什么?
答案 0 :(得分:1641)
如果我想基于一个或更多属性获取不同的列表,该怎么办?
简单!你想把它们分组并从小组中挑选一个胜利者。
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();
答案 1 :(得分:1081)
编辑:现在是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
构造函数。
答案 2 :(得分:71)
如果您希望它看起来像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();
答案 3 :(得分:59)
使用:
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
执行不同的功能。
答案 4 :(得分:55)
我认为这已经足够了:
list.Select(s => s.MyField).Distinct();
答案 5 :(得分:32)
按字段解决第一组,然后选择firstordefault项目。
List<Person> distinctPeople = allPeople
.GroupBy(p => p.PersonId)
.Select(g => g.FirstOrDefault())
.ToList();
答案 6 :(得分:24)
您可以使用标准Linq.ToLookup()
执行此操作。这将为每个唯一键创建一组值。只需选择集合中的第一项
Persons.ToLookup(p => p.Id).Select(coll => coll.First());
答案 7 :(得分:11)
我写了一篇文章解释了如何扩展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.
以下是文章: Extending LINQ - Specifying a Property in the Distinct Function
答案 8 :(得分:5)
你可以这样做(尽管不是闪电般的):
people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));
即,“选择列表中没有其他人的所有人使用相同的ID。”
请注意,在你的例子中,那只会选择人3.我不知道如何分辨出你想要的东西,而不是前两个。
答案 9 :(得分:5)
如果您需要对多个属性使用Distinct方法,可以查看我的PowerfulExtensions库。目前它处于一个非常年轻的阶段,但你已经可以使用Distinct,Union,Intersect等方法,除了任何数量的属性;
这是您使用它的方式:
using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);
答案 10 :(得分:4)
当我们在项目中遇到这样的任务时,我们定义了一个小的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 in LINQ 。
答案 11 :(得分:4)
我个人使用以下课程:
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实现......)
答案 12 :(得分:3)
如果您不想将MoreLinq库添加到项目中以获得DistinctBy
功能,那么您可以使用Linq&#39; {{1}的重载获得相同的最终结果接受Distinct
参数的方法。
首先创建一个通用的自定义相等比较器类,它使用lambda语法对泛型类的两个实例进行自定义比较:
IEqualityComparer
然后在主代码中使用它:
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
集合不包含任何null元素如果集合可以包含空值,那么只需重写lambdas以检查null,例如:
people
修改强>
这种方法类似于弗拉基米尔·内斯特罗夫斯基的答案,但更简单。
它也与Joel的答案类似,但允许涉及多个属性的复杂比较逻辑。
但是,如果您的对象只能在Func<Person, Person, bool> areEqual = (p1, p2) =>
{
return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false;
};
之间有所不同,那么另一位用户会给出正确答案,您需要做的就是覆盖您Id
和GetHashCode()
的默认实施Equals()
类,然后使用Linq的开箱即用Person
方法过滤掉任何重复项。
答案 13 :(得分:2)
List<Person>lst=new List<Person>
var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();
答案 14 :(得分:2)
与其他.NET版本兼容的最佳方法是重写Equals和GetHash来处理这个问题(请参阅Stack Overflow问题 This code returns distinct values. However, what I want is to return a strongly typed collection as opposed to an anonymous type ),但如果需要在整个代码中通用的东西,本文中的解决方案都很棒。
答案 15 :(得分:2)
随着即将发布的 .NET 6,有使用 the new DistinctBy()
extension in Linq 的新解决方案,因此,从 .NET 6 开始,我们可以做到
var distinctPersonsById = personList.DistinctBy(x => x.Id);
答案 16 :(得分:2)
覆盖等于(对象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();
答案 17 :(得分:0)
你应该能够在人物上重叠等于实际在Person.id上做Equals。这应该导致你所追求的行为。
答案 18 :(得分:0)
您可以使用DistinctBy()通过对象属性获取Distinct记录。只需在使用前添加以下语句即可:
使用Microsoft.Ajax.Utilities;
然后按如下所示使用它:
super()
其中“索引”是我希望数据与众不同的属性。
答案 19 :(得分:-2)
如果您使用旧的 .NET 版本,其中没有内置扩展方法,那么您可以定义自己的扩展方法:
fmap f = invmap f undefined
用法示例:
Invariant
答案 20 :(得分:-3)
请尝试以下代码。
var Item = GetAll().GroupBy(x => x .Id).ToList();