关于特定属性上的c:linq的distinct()。

LINQ's Distinct() on a particular property

我在玩linq来了解它,但是我不知道当我没有一个简单的列表时如何使用distinct(一个简单的整数列表很容易做到,这不是问题)。如果我想在对象的一个或多个属性的列表中使用distinct,该怎么办?

示例:如果对象是Person,则属性为Id。我怎样才能得到所有的人,并在他们身上使用对象的财产Id

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

因此,要仅使用Id属性查找不同的值,可以使用:

1
var query = people.DistinctBy(p => p.Id);

要使用多个属性,可以使用匿名类型,这些类型适当地实现相等:

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

未经测试,但它应该可以工作(现在至少可以编译)。

不过,它假定键的默认比较器-如果要传入相等比较器,只需将其传递给HashSet构造函数。


如果希望查询语法看起来像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());

where帮助您过滤条目(可能更复杂),groupbyselect执行不同的功能。


我认为这已经足够了:

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

您可以使用标准的Linq.ToLookup()来实现这一点。这将为每个唯一键创建一个值集合。只需选择集合中的第一项

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

我发现使用这种方法的好处是,对于接受IEqualityComparer的其他方法重新使用LambdaEqualityComparer类。(噢,我把yield的东西留给了最初的linq实现…)


如果您不想将morelinq库添加到您的项目中只是为了获得DistinctBy功能,那么您可以使用接收IEqualityComparer参数的linq的Distinct方法的重载来获得相同的最终结果。

首先创建一个通用自定义相等比较器类,该类使用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的答案,但允许涉及多个属性的复杂比较逻辑。

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


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

要做到这一点,最好的方法是重写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
List<Person> distinctList = new[] { person1, person2, person3 }.Distinct().ToList();


可以使用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();