LINQ对特定属性的Distinct()

时间:2009-01-28 20:45:09

标签: c# linq .net-3.5 distinct

我正在玩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列表的最佳方法是什么?

21 个答案:

答案 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可帮助您过滤条目(可能更复杂),groupbyselect执行不同的功能。

答案 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; }; 之间有所不同,那么另一位用户会给出正确答案,您需要做的就是覆盖您IdGetHashCode()的默认实施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();