LINQ对特定属性的Distinct()

Pat*_*ins 1002 c# linq distinct .net-3.5

我正在玩LINQ来了解它,但是当我没有一个简单的列表时,我无法弄清楚如何使用Distinct(一个简单的整数列表很容易做到,这不是问题).我想在对象的一个多个属性上使用对象列表中的区别

示例:如果对象是Person,则使用Property Id.如何获取所有Person并使用对象Distinct的属性Id

Person1: Id=1, Name="Test1"
Person2: Id=1, Name="Test1"
Person3: Id=2, Name="Test2"
Run Code Online (Sandbox Code Playgroud)

我怎样才能得到Person1和Person3?那可能吗?

如果LINQ不可能,那么Person在.NET 3.5 中依赖于某些属性的列表最好的方法是什么?

Amy*_*y B 1733

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

简单!你想把它们分组并从小组中挑出一个胜利者.

List<Person> distinctPeople = allPeople
  .GroupBy(p => p.PersonId)
  .Select(g => g.First())
  .ToList();
Run Code Online (Sandbox Code Playgroud)

如果要在多个属性上定义组,请按以下步骤操作:

List<Person> distinctPeople = allPeople
  .GroupBy(p => new {p.PersonId, p.FavoriteColor} )
  .Select(g => g.First())
  .ToList();
Run Code Online (Sandbox Code Playgroud)

  • @ChocapicSz不.当源有多个项目时,每次抛出"Single()"和"SingleOrDefault()".在此操作中,我们预计每个组可能有多个项目.就此而言,`First()`比`FirstOrDefault()`更受欢迎,因为每个组必须至少有一个成员....除非你使用的是EntityFramework,它无法弄清楚每个组至少有一个成员和要求`FirstOrDefault()`. (21认同)
  • 我试了一下它应该改为Select(g => g.FirstOrDefault()) (7认同)
  • 目前 EF Core 似乎不支持,即使使用 `FirstOrDefault()` https://github.com/dotnet/efcore/issues/12088 我使用的是 3.1,并且收到“无法翻译”错误。 (7认同)
  • 非常好的答案!Realllllly帮助我从Linq-to-Entities驱动的sql视图,我无法修改视图.我需要使用FirstOrDefault()而不是First() - 一切都很好. (5认同)
  • @ErenEersonmez 当然。使用我发布的代码,如果需要延迟执行,请取消 ToList 调用。 (2认同)

Jon*_*eet 1152

编辑:这是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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

var query = people.DistinctBy(p => p.Id);
Run Code Online (Sandbox Code Playgroud)

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

var query = people.DistinctBy(p => new { p.Id, p.Name });
Run Code Online (Sandbox Code Playgroud)

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

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

  • @ ashes999:如果你只在一个地方做这件事,那么肯定,使用`GroupBy`更简单.如果你需要在不止一个地方,那么封装意图就更清晰了(IMO). (8认同)
  • Source to DistinctBy:http://code.google.com/p/morelinq/source/browse/MoreLinq/DistinctBy.cs (7认同)
  • @MatthewWhited:鉴于这里没有提到`IQueryable <T>`,我看不出它是如何相关的.我同意这不适合EF等,但在LINQ to Objects中我认为它比'GroupBy`更合适.问题的背景总是很重要. (5认同)
  • 该项目在github上移动,这里是DistinctBy的代码:https://github.com/morelinq/MoreLINQ/blob/master/MoreLinq/DistinctBy.cs (4认同)
  • 我认为这是一个优于众多 `GroupBy()`/`group by`/`ToLookup()` 答案的解决方案,因为像 `Distinct()` 一样,它能够在遇到元素时立即`产生`元素_ (第一次),而其他方法在_整个输入序列被消耗_之前无法返回任何内容。我认为这是一个重要的,呃,_区别_值得在答案中指出。另外,就内存而言,到最后一个元素,这个“HashSet&lt;&gt;”将仅存储“唯一”元素,而其他方法将在某个地方存储具有“唯一+重复”元素的“唯一”组。 (3认同)
  • @ashes999:我不确定你的意思。代码出现在库中的 *​​ 和 * 答案中 - 取决于您是否愿意接受依赖。 (2认同)

Mag*_*ron 82

从 .NET 6 开始,有使用Linq 中DistinctBy()扩展的新解决方案,因此我们可以执行以下操作:

var distinctPersonsById = personList.DistinctBy(x => x.Id);
Run Code Online (Sandbox Code Playgroud)

方法的签名DistinctBy

// 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);
Run Code Online (Sandbox Code Playgroud)


Chu*_*nce 74

如果您希望它看起来像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();
Run Code Online (Sandbox Code Playgroud)

  • 嗯,我的想法是查询语法和流畅的API语法就像LINQ一样,并且只是人们使用的偏好.我自己更喜欢流畅的API,所以我会考虑更多LINK-Like但我认为这是主观的 (4认同)

小智 67

使用:

List<Person> pList = new List<Person>();
/* Fill list */

var result = pList.Where(p => p.Name != null).GroupBy(p => p.Id).Select(grp => grp.FirstOrDefault());
Run Code Online (Sandbox Code Playgroud)

where帮助你过滤条目(可能更加复杂)和groupbyselect执行不同的功能.

  • 完美,无需扩展 Linq 或使用其他依赖项即可工作。 (3认同)

小智 59

我认为这就足够了:

list.Select(s => s.MyField).Distinct();
Run Code Online (Sandbox Code Playgroud)

  • 如果他需要支持他的完整对象,而不仅仅是那个特定领域呢? (38认同)

cah*_*yaz 37

首先按字段解决第一组,然后选择firstordefault项.

    List<Person> distinctPeople = allPeople
   .GroupBy(p => p.PersonId)
   .Select(g => g.FirstOrDefault())
   .ToList();
Run Code Online (Sandbox Code Playgroud)


Dav*_*der 24

您可以使用标准执行此操作Linq.ToLookup().这将为每个唯一键创建一组值.只需选择集合中的第一个项目即可

Persons.ToLookup(p => p.Id).Select(coll => coll.First());
Run Code Online (Sandbox Code Playgroud)


Con*_*ngo 17

以下代码在功能上等同于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)));
}
Run Code Online (Sandbox Code Playgroud)

最后,查看Jon Skeet在Google Code上的最新版DistinctBy.cs.

  • 这给了我一个"序列没有值错误",但Skeet的答案产生了正确的结果. (3认同)

Tim*_*uri 10

我写了一篇文章,解释了如何扩展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.
Run Code Online (Sandbox Code Playgroud)

这是文章:扩展LINQ - 在不同的函数中指定属性

  • 你的链接坏了 (6认同)
  • 你的文章有一个错误,应该有一个<T>在Distinct之后:public static IEnumerable <T> Distinct(这个...它看起来不像它会更好地工作(很好)在一个属性上,即第一个组合和姓氏. (3认同)
  • +1,一个小错误不足以成为downvote的理由,只是如此愚蠢,常常打字错字.我还没有看到一个适用于任何数量的属性的通用函数!我希望downvoter也在这个帖子中对所有其他答案进行了低估.但是,嘿这第二种类型是什么?我反对 ! (2认同)

mqp*_*mqp 5

您可以这样做(尽管不是很快):

people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));
Run Code Online (Sandbox Code Playgroud)

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

请注意,在您的示例中,您只会选择第3个人。我不确定在前两个中如何辨别您想要哪个。


And*_*Gis 5

如果您需要对多个属性使用Distinct方法,可以查看我的PowerfulExtensions库.目前它处于一个非常年轻的阶段,但你已经可以使用Distinct,Union,Intersect等方法,除了任何数量的属性;

这是你如何使用它:

using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);
Run Code Online (Sandbox Code Playgroud)


Vla*_*sky 5

当我们在项目中面对这样的任务时,我们定义了一个小的API来构成比较器。

因此,用例是这样的:

var wordComparer = KeyEqualityComparer.Null<Word>().
    ThenBy(item => item.Text).
    ThenBy(item => item.LangID);
...
source.Select(...).Distinct(wordComparer);
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

更多详细信息,请访问我们的网站:LINQ中的IEqualityComparer


Joe*_*oel 5

我个人使用以下课程:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,扩展方法:

public static IEnumerable<TSource> Distinct<TSource, TCompare>(
    this IEnumerable<TSource> source, Func<TSource, TCompare> selector)
{
    return source.Distinct(new LambdaEqualityComparer<TSource, TCompare>(selector));
}
Run Code Online (Sandbox Code Playgroud)

最后,预期用途:

var dates = new List<DateTime>() { /* ... */ }
var distinctYears = dates.Distinct(date => date.Year);
Run Code Online (Sandbox Code Playgroud)

我发现使用此方法的优点是将LambdaEqualityComparer类重新用于接受的其他方法IEqualityComparer。(哦,我把yield东西留给了原始的LINQ实现...)


Har*_*eem 5

您可以使用DistinctBy()通过对象属性获取Distinct记录。只需在使用前添加以下语句即可:

使用Microsoft.Ajax.Utilities;

然后像下面这样使用它:

var listToReturn = responseList.DistinctBy(x => x.Index).ToList();
Run Code Online (Sandbox Code Playgroud)

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