首页 文章

LINQ对特定属性的Distinct()

提问于
浏览
840

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

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

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

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

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

19 回答

  • 1

    使用:

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

  • 43

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

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

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

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

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

    以下代码在功能上等同于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 .

  • 20

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

  • 0

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

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

    与其他.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),但如果您需要在整个代码中使用通用的东西,那么本文中的解决方案是大 .

  • 0

    我认为这就足够了:

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

    我个人使用以下类:

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

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

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

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

    这是你如何使用它:

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

    EDIT :现在是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

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

  • -1

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

  • 1548

    如果您不想将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));
    

    瞧! :)

    以上假设如下:

    • 属性 Person.Id 的类型为 int

    • people 集合不包含任何null元素

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

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

    EDIT

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

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

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

  • 2

    请试试下面的代码 .

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

    覆盖 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();
    
  • 988

    你可以这样做(尽管不是闪电般快速):

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

    也就是说,“选择列表中没有其他不同人的所有人使用相同的ID . ”

    在你的例子中,请注意,只选择人3.我不知道如何分辨出你想要的东西,而不是前两个 .

相关问题