ChatGPT解决这个技术问题 Extra ChatGPT

LINQ 在特定属性上的 Distinct()

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

示例:如果对象是 Person,则具有属性 Id。如何使用对象的属性 Id 获取所有 Person 并在其上使用 Distinct

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

我怎样才能只得到 Person1Person3?那可能吗?

如果 LINQ 无法实现,那么根据 .NET 3.5 中的某些属性来获得 Person 列表的最佳方法是什么?


F
Frederik Struck-Schøning

如果我想获得基于一个或多个属性的不同列表怎么办?

简单的!您想将它们分组并从组中选出获胜者。

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


@ErenEersonmez 当然。使用我发布的代码,如果需要延迟执行,请不要使用 ToList 调用。
非常好的答案! Realllllly 在我无法修改视图的 sql 视图驱动的 Linq-to-Entities 中帮助了我。我需要使用 FirstOrDefault() 而不是 First() - 一切都很好。
我试过了,它应该变成 Select(g => g.FirstOrDefault())
@ChocapicSz 不。当源有多个项目时,Single()SingleOrDefault() 都会抛出。在此操作中,我们预计每个组可能有多个项目的可能性。就此而言,First() 优于 FirstOrDefault(),因为每个组必须至少有一个成员....除非您使用 EntityFramework,它无法确定每个组至少有一个成员并要求 { 4}。
EF Core 目前似乎不支持,即使使用 FirstOrDefault() github.com/dotnet/efcore/issues/12088 我在 3.1 上,我得到“无法翻译”错误。
T
Thijs

编辑:现在是 MoreLINQ 的一部分。

你需要的是一个有效的“distinct-by”。我不相信它是 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 构造函数。


@ashes999:我不确定你的意思。该代码存在于答案和库中 - 取决于您是否愿意承担依赖关系。
@ashes999:如果您只在一个地方执行此操作,那么可以肯定,使用 GroupBy 更简单。如果您在多个地方需要它,那么封装意图会更清洁(IMO)。
@MatthewWhited:鉴于这里没有提到 IQueryable<T>,我看不出它有什么相关性。我同意这不适合 EF 等,但在 LINQ to Objects 中,我认为它比GroupBy适合。问题的背景总是很重要的。
项目移到 github 上,DistinctBy 的代码如下:github.com/morelinq/MoreLINQ/blob/master/MoreLinq/DistinctBy.cs
我认为这是一个优于众多 GroupBy()/group by/ToLookup() 答案的解决方案,因为与 Distinct() 一样,它能够yield遇到一个元素 (第一次),而那些其他方法在整个输入序列被消耗完之前不能返回任何东西。我认为这是一个重要的,呃,区别值得在答案中指出。此外,就内存而言,通过最后一个元素,此 HashSet<> 将仅存储 unique 元素,而其他方法将在某处存储具有 unique + duplicates 元素的 unique 组。
S
Sheridan

利用:

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


完美,无需扩展 Linq 或使用其他依赖项即可工作。
b
burnttoast11

如果你希望它看起来像 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();

嗯,我的想法是查询语法和流畅的 API 语法就像 LINQ 一样,而且它只是偏爱人们使用的语法。我自己更喜欢流利的 API,所以我会考虑更像 LINK 但我想这是主观的
LINQ-Like 与偏好无关,“类似 LINQ”与看起来像是嵌入到 C# 中的不同查询语言有关,我更喜欢来自 java 流的流畅接口,但它不是 LINQ-Like。
出色的!!你是我的英雄!
I
Ivan

我认为这就足够了:

list.Select(s => s.MyField).Distinct();

如果他需要返回他的全部对象,而不仅仅是那个特定的领域怎么办?
具有相同属性值的几个对象究竟是什么对象?
H
Himanshu

解决方案首先按您的字段分组,然后选择 FirstOrDefault 项。

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

P
Peter Mortensen

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

Persons.ToLookup(p => p.Id).Select(coll => coll.First());

T
Theodor Zoulias

从 .NET 6 开始,有使用 the new DistinctBy() extension in Linq 的新解决方案,所以我们可以这样做:

var distinctPersonsById = personList.DistinctBy(x => x.Id);

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

C
Contango

以下代码在功能上等同于 Jon Skeet's answer

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

顺便说一下,请查看 Jon Skeet's latest version of DistinctBy.cs on Google Code

2022-04-03 更新

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


这给了我一个“序列没有值错误”,但 Skeet 的回答产生了正确的结果。
为了澄清为什么这不等同于 Jon Skeet 的答案 - 只有当你重用相同的枚举时才会发生差异。如果您重用此答案中的可枚举项,则 HashSet 已填充,因此不会返回任何元素(已看到所有键)。对于 Skeet 的回答,由于它使用 yield return,因此每次迭代可枚举时它都会创建一个新的 HashSet。
@AndrewMcClement 同意。更新了答案。
N
Nowhere Man

我写了一篇文章,解释了如何扩展 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


您的文章有错误,在 Distinct 之后应该有一个 : public static IEnumerable Distinct(this... 而且它看起来不会(很好地)在多个属性上工作,即第一个属性的组合和姓氏。
请不要在外部链接中发布相关信息,an answer must stand on its own。可以发布链接,但请将相关信息复制到答案本身。您只发布了一个使用示例,但没有外部资源它是无用的。
J
Joel

我个人使用以下类:

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 实现......)


H
Harry .Naeem

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

使用 Microsoft.Ajax.Utilities;

然后像下面这样使用它:

var listToReturn = responseList.DistinctBy(x => x.Index).ToList();

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


m
mqp

你可以这样做(虽然不是闪电般快速)像这样:

people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));

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

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


P
Peter Mortensen

如果您需要对多个属性使用 Distinct 方法,可以查看我的 PowerfulExtensions 库。目前它还处于非常年轻的阶段,但您已经可以在任意数量的属性上使用 Distinct、Union、Intersect、Except 等方法;

这是你如何使用它:

using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);

P
Peter Mortensen

当我们在项目中遇到这样的任务时,我们定义了一个小的 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


C
Caspian Canuck

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

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

瞧! :)

以上假设如下:

属性 Person.Id 的类型为 int

people 集合不包含任何 null 元素

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

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

编辑

这种方法类似于 Vladimir Nesterovsky 的答案,但更简单。

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

但是,如果您的对象只能相差 Id,那么另一个用户给出了正确答案,您需要做的就是覆盖 Person 类中 GetHashCode()Equals() 的默认实现,然后只需使用Linq 的开箱即用 Distinct() 方法可过滤掉任何重复项。


我只想获取字典中的唯一项目,您能帮忙吗,我正在使用此代码 If TempDT IsNot Nothing Then m_ConcurrentScriptDictionary = TempDT.AsEnumerable.ToDictionary(Function(x) x.SafeField(fldClusterId, NULL_ID_VALUE), Function(y) y.SafeField(fldParamValue11, NULL_ID_VALUE))
W
Waldemar Gałęzinowski

覆盖 Equals(object 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();

但是 GetHashCode() 应该更高级(也算名称),我认为这个答案可能是最好的。实际上,要归档目标逻辑,不需要重写 GetHashCode(),Equals() 就足够了,但是如果我们需要性能,我们必须重写它。所有比较算法,首先检查哈希,如果它们相等则调用 Equals()。
此外,在 Equals() 中,第一行应该是“if (!(obj is Person)) return false”。但最佳实践是使用转换为类型的单独对象,例如“var o = obj as Person;if (o==null) return false;”然后用 o 检查相等而不进行强制转换
像这样覆盖 Equals 不是一个好主意,因为它可能会对其他期望 Person 的 Equality 取决于多个属性的程序员产生意想不到的后果。
C
Community

与其他 .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),但如果您需要通篇通用的东西您的代码,本文中的解决方案很棒。


A
Arindam
List<Person>lst=new List<Person>
        var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();

您的意思是 Select() new Person 而不是 new Player?但是,您按 ID 订购的事实并不会以某种方式通知 Distinct() 使用该属性来确定唯一性,因此这是行不通的。
G
GWLlosa

您应该能够覆盖 Person 上的 Equals 以在 Person.id 上实际执行 Equals。这应该会导致您所追求的行为。


我不会推荐这种方法。虽然它可能在这种特定情况下有效,但这只是一种不好的做法。如果他想通过其他地方的不同属性来区分怎么办?他肯定不能两次覆盖 Equals,对吗? :-) 除此之外,为此目的重写 equals 是根本错误的,因为它是为了判断两个对象是否相等。如果平等的班级条件因任何原因发生变化,你肯定会烧伤你的手指......
T
TOL

如果你使用旧的 .NET 版本,扩展方法不是内置的,那么你可以定义你自己的扩展方法:

public static class EnumerableExtensions
{
    public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> enumerable, Func<T, TKey> keySelector)
    {
        return enumerable.GroupBy(keySelector).Select(grp => grp.First());
    }
}

使用示例:

var personsDist = persons.DistinctBy(item => item.Name);

这如何改进提供相同扩展方法但实现方式略有不同的公认答案?
至少它更短。这不是一点点,它的实施方式不同。
而且不是更好。接受的答案要好得多。为什么要提供劣质的解决方案?旧问题的新答案应该是对已有问题的重大改进。
B
Bose_geek

可能这会有所帮助,试试这个。使用 HashSet 的代码性能更高。

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    var known = new HashSet<TKey>();
    return source.Where(element => known.Add(keySelector(element)));
}

A
Alien

请尝试使用以下代码。

var Item = GetAll().GroupBy(x => x .Id).ToList();

欢迎使用简短的回答,但它不会为试图了解问题背后原因的后者用户提供太多价值。请抽出一些时间来解释导致问题的真正问题以及如何解决。谢谢~