我玩LINQ来了解它,但我不知道如何使用鲜明当我没有一个简单的列表(一个简单的整数列表是很容易做到的,这不是问题)。如果我想使用鲜明的列表<TElement>上的一个或多个属性的TElement?

示例:如果一个对象是Person,具有属性Id。我怎么能得到所有人,并使用鲜明对他们与对象的属性Id ?

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

如何得到Person1和Person3?这可能吗?

如果用LINQ是不可能的,那么根据Person的某些属性获得Person列表的最佳方法是什么?


当前回答

下面的代码在功能上等同于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)));
}

顺便说一句,请在谷歌Code上查看Jon Skeet的最新版本的DistinctBy.cs。

更新2022-04-03

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

其他回答

如果我想根据一个或多个属性获得一个不同的列表,该怎么办?

简单!你想把他们分组,然后从中选出一个优胜者。

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

与其他。net版本兼容的最好方法是重写Equals和GetHash来处理这个问题(参见堆栈溢出问题这段代码返回不同的值。然而,我想要的是返回一个强类型集合,而不是匿名类型),但是如果您需要在整个代码中使用通用的东西,那么本文中的解决方案非常好。

Use:

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

解决方案第一组由您的字段,然后选择FirstOrDefault项。

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

如果你不想将MoreLinq库添加到你的项目中只是为了获得DistinctBy功能,那么你可以使用重载Linq的Distinct方法获得相同的最终结果,该方法采用了一个IEqualityComparer参数。

首先创建一个通用的自定义相等比较器类,它使用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));

瞧!:)

以上假设如下:

财产的人。Id的类型是int people集合不包含任何空元素

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

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

EDIT

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

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

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