【问题标题】:How to get distinct instance from a list by Lambda or LINQ如何通过 Lambda 或 LINQ 从列表中获取不同的实例
【发布时间】:2010-11-14 01:56:03
【问题描述】:

我有这样的课:

class MyClass<T> {
    public string value1 { get; set; }
    public T objT { get; set; }
}

和这个类的列表。我想使用 .net 3.5 lambda 或 linq 通过不同的 value1 获取 MyClass 列表。我想这是可能的,而且比 .net 2.0 中缓存这样的列表的方式要简单得多:

List<MyClass<T>> list; 
...
List<MyClass<T>> listDistinct = new List<MyClass<T>>();
foreach (MyClass<T> instance in list)
{
    // some code to check if listDistinct does contain obj with intance.Value1
    // then listDistinct.Add(instance);
}

什么是 lambda 或 LINQ 方法?

【问题讨论】:

    标签: c# linq .net-3.5 lambda


    【解决方案1】:

    Marcdahlbyk 的答案似乎都非常有效。我有一个更简单的解决方案。您可以使用GroupBy,而不是使用Distinct。它是这样的:

    var listDistinct
        = list.GroupBy(
            i => i.value1,
            (key, group) => group.First()
        ).ToArray();
    

    请注意,我已将两个函数传递给 GroupBy()。第一个是键选择器。第二个从每个组中只获得一个项目。根据您的问题,我认为First() 是正确的。如果你愿意,你可以写一个不同的。你可以试试Last() 看看我的意思。

    我使用以下输入进行了测试:

    var list = new [] {
        new { value1 = "ABC", objT = 0 },
        new { value1 = "ABC", objT = 1 },
        new { value1 = "123", objT = 2 },
        new { value1 = "123", objT = 3 },
        new { value1 = "FOO", objT = 4 },
        new { value1 = "BAR", objT = 5 },
        new { value1 = "BAR", objT = 6 },
        new { value1 = "BAR", objT = 7 },
        new { value1 = "UGH", objT = 8 },
    };
    

    结果是:

    //{ value1 = ABC, objT = 0 }
    //{ value1 = 123, objT = 2 }
    //{ value1 = FOO, objT = 4 }
    //{ value1 = BAR, objT = 5 }
    //{ value1 = UGH, objT = 8 }
    

    我还没有测试它的性能。我相信这个解决方案可能比使用Distinct 的解决方案慢一点。尽管有这个缺点,但有两个很大的优点:简单性和灵活性。通常,优先考虑简单而不是优化更好,但这实际上取决于您要解决的问题。

    【讨论】:

    • 非常有趣。实际上,Comparer 方法有一个限制:它只返回第一个找到的不同的。如果我需要灵活性,请在第二个、...或最后一个获得不同,不确定 group.xxx() 是否能够做到?
    • 是的,你会的。只需将First() 替换为Last() 并查看。当然,如果需要,您可以进行任何其他复杂的选择。
    • @David:你应该考虑接受这个答案。它是解决您问题的灵活而优雅的解决方案。
    • 这让我很头疼。谢谢!
    • @RollRoll 然后你根据匿名类做复合键:data.GroupBy(x => new { x.prop1, x.prop2 })。
    【解决方案2】:

    嗯...我可能会写一个自定义的IEqualityComparer&lt;T&gt; 以便我可以使用:

    var listDistinct = list.Distinct(comparer).ToList();
    

    并通过 LINQ 编写比较器....

    可能有点矫枉过正,但至少可以重复使用:

    先使用:

    static class Program {
        static void Main() {
            var data = new[] {
                new { Foo = 1,Bar = "a"}, new { Foo = 2,Bar = "b"}, new {Foo = 1, Bar = "c"}
            };
            foreach (var item in data.DistinctBy(x => x.Foo))
                Console.WriteLine(item.Bar);
            }
        }
    }
    

    使用实用方法:

    public static class ProjectionComparer
    {
        public static IEnumerable<TSource> DistinctBy<TSource,TValue>(
            this IEnumerable<TSource> source,
            Func<TSource, TValue> selector)
        {
            var comparer = ProjectionComparer<TSource>.CompareBy<TValue>(
                selector, EqualityComparer<TValue>.Default);
            return new HashSet<TSource>(source, comparer);
        }
    }
    public static class ProjectionComparer<TSource>
    {
        public static IEqualityComparer<TSource> CompareBy<TValue>(
            Func<TSource, TValue> selector)
        {
            return CompareBy<TValue>(selector, EqualityComparer<TValue>.Default);
        }
        public static IEqualityComparer<TSource> CompareBy<TValue>(
            Func<TSource, TValue> selector,
            IEqualityComparer<TValue> comparer)
        {
            return new ComparerImpl<TValue>(selector, comparer);
        }
        sealed class ComparerImpl<TValue> : IEqualityComparer<TSource>
        {
            private readonly Func<TSource, TValue> selector;
            private readonly IEqualityComparer<TValue> comparer;
            public ComparerImpl(
                Func<TSource, TValue> selector,
                IEqualityComparer<TValue> comparer)
            {
                if (selector == null) throw new ArgumentNullException("selector");
                if (comparer == null) throw new ArgumentNullException("comparer");
                this.selector = selector;
                this.comparer = comparer;
            }
    
            bool IEqualityComparer<TSource>.Equals(TSource x, TSource y)
            {
                if (x == null && y == null) return true;
                if (x == null || y == null) return false;
                return comparer.Equals(selector(x), selector(y));
            }
    
            int IEqualityComparer<TSource>.GetHashCode(TSource obj)
            {
                return obj == null ? 0 : comparer.GetHashCode(selector(obj));
            }
        }
    }
    

    【讨论】:

    • 关于代码的一个问题:ProjectionComparer 是什么? .Net 类或 LINQ 或 IEnumerable 相关类,以便您可以自定义扩展?
    • 好的。我认为“ProjectionComparer”是您定义的任何类名,但在该类中您已将扩展方法 DistinctBy() 自定义为 IEnumerable,并且 ProjectionComparer 是另一个帮助器类,对吗? ProjectionComparer 可以是不同的名称,而不是相同的名称吗?
    • 如果我想获取 MyClass 的 value1 列表,我可以像这样使用这个比较器: List listValue1s = list.Distinct(comparer).ToList().Select(y => y.value1);对吗?
    • ProjectionComparer 的名称无关紧要 - 您可以将其称为 EnumerableExtensions。 ProjectionComparer 之所以如此命名,是因为它通过投影提供了一个比较器,这是基于现有值(例如来自 MyClass 的 value1)获取新值的常用术语。对于你的最后一个问题:除非你需要,否则不要调用 ToList() 。如果你不打算使用 MyClass 对象的不同列表,那么你最好让你的 value1 像这样: IEnumerable value1s = list.Select(y => y.value1).Distinct( );
    • Marc,你有关于 jpbochi 的替代方法的 cmet 吗?似乎不需要写一个Comparer扩展类,而且很灵活。对于 LINQ-to-Object 的情况,似乎已经足够好了。
    【解决方案3】:

    你可以使用这个扩展方法:

        IEnumerable<MyClass> distinctList = sourceList.DistinctBy(x => x.value1);
    
        public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
            this IEnumerable<TSource> source,
            Func<TSource, TKey> keySelector)
        {
            var knownKeys = new HashSet<TKey>();
            return source.Where(element => knownKeys.Add(keySelector(element)));
        }
    

    【讨论】:

    • 这似乎可行,但是当调用该方法并且没有立即评估时,例如使用ToList(),返回的可枚举在第一次评估时看起来正确,但在第二次评估后,可枚举为空。比如说,如果我有一个包含 4 个对象和 1 个重复项的列表,请调用此方法来获取一个具有 3 个对象的枚举。调用.Count() 返回3 然后再次调用它返回0。有什么想法吗?
    【解决方案4】:

    查看Enumerable.Distinct(),它可以接受一个 IEqualityComparer:

    class MyClassComparer<T> : IEqualityComparer<MyClass<T>>
    {
        // Products are equal if their names and product numbers are equal.
        public bool Equals(MyClass<T> x, MyClass<T>y)
        {
            // Check whether the compared objects reference the same data.
            if (Object.ReferenceEquals(x, y)) return true;
    
            // Check whether any of the compared objects is null.
            if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
                return false;
    
            // Check whether the products' properties are equal.
            return x.value1 == y.value1;
        }
    
        // If Equals() returns true for a pair of objects,
        // GetHashCode must return the same value for these objects.
    
        public int GetHashCode(MyClass<T> x)
        {
            // Check whether the object is null.
            if (Object.ReferenceEquals(x, null)) return 0;
    
            // Get the hash code for the Name field if it is not null.
            return (x.value1 ?? "").GetHashCode();
        }
    }
    

    您的代码 sn-p 可能如下所示:

    List<MyClass<T>> list; 
    ...
    List<MyClass<T>> listDistinct = list.Distinct(new MyClassComparer<T>).ToList();
    

    【讨论】:

    • 我认为每种方法都有优点。 group-by 方法需要最少的代码并且可以更灵活,但具有(轻微的)性能损失,并且乍一看代码的目的并不那么明显。 Marc 的通用解决方案读起来很流畅,但有些人可能会说单个表达​​式做得太多:它既指定了项目的比较方式,又执行了实际的 select-distinct。我的方法更具体,但在等效逻辑和利用它的操作之间提供了清晰的分离。
    • 感谢您完整的 cmets。我同意你的可读性和分离性。然而,就在第二个或最后一个获得 T 实例的灵活性而言,Comparer 只获得第一个,并且它可能会复杂到相同的灵活性,对吧?在 jpbochi 上查看我的 cmets。
    • 确实,Distinct-with-Comparer 方法只会返回“集合”中的第一个。但是,我认为“不同”的语义是,如果对象符合您的标准,则它们应被视为等效。一旦你开始选择第一个或最后一个,你就真的从一个“不同的”计算转移到一个分组上的某种聚合(First、Last、Min 等等)。
    【解决方案5】:

    这样会更简单...

    var distinctList = list.GroupBy(l => l.value1, (key, c) => l.FirstOrDefault());
    

    【讨论】:

    • 应该是 c.FirstOrDefault() 而不是 l.FirstOrDefault()this 答案中添加了类似的评论。
    【解决方案6】:

    在 linq 中这是更先进的分组

    list.GroupBy(li => li.value, (key, grp) => li.FirstOrDefault());
    

    【讨论】:

    • 我相信它实际上应该是:list.GroupBy(li => li.value, (key, grp) => grp.First());
    【解决方案7】:

    我接受了 Marc 的回答,将其修复为使用 TSource 作为值类型(测试 default(TSource) 而不是 null),清理了一些冗余类型规范,并为它编写了一些测试。这是我今天使用的。感谢 Marc 的好主意和实施。

    public static class LINQExtensions
    {
        public static IEnumerable<TSource> DistinctBy<TSource, TValue>(
            this IEnumerable<TSource> source,
            Func<TSource, TValue> selector)
        {
            var comparer = ProjectionComparer<TSource>.CompareBy(
                selector, EqualityComparer<TValue>.Default);
            return new HashSet<TSource>(source, comparer);
        }
    }
    public static class ProjectionComparer<TSource>
    {
        public static IEqualityComparer<TSource> CompareBy<TValue>(
            Func<TSource, TValue> selector)
        {
            return CompareBy(selector, EqualityComparer<TValue>.Default);
        }
        public static IEqualityComparer<TSource> CompareBy<TValue>(
            Func<TSource, TValue> selector,
            IEqualityComparer<TValue> comparer)
        {
            return new ComparerImpl<TValue>(selector, comparer);
        }
        sealed class ComparerImpl<TValue> : IEqualityComparer<TSource>
        {
            private readonly Func<TSource, TValue> _selector;
            private readonly IEqualityComparer<TValue> _comparer;
            public ComparerImpl(
                Func<TSource, TValue> selector,
                IEqualityComparer<TValue> comparer)
            {
                if (selector == null) throw new ArgumentNullException("selector");
                if (comparer == null) throw new ArgumentNullException("comparer");
                _selector = selector;
                _comparer = comparer;
            }
    
            bool IEqualityComparer<TSource>.Equals(TSource x, TSource y)
            {
                if (x.Equals(default(TSource)) && y.Equals(default(TSource)))
                {
                    return true;
                }
    
                if (x.Equals(default(TSource)) || y.Equals(default(TSource)))
                {
                    return false;
                }
                return _comparer.Equals(_selector(x), _selector(y));
            }
    
            int IEqualityComparer<TSource>.GetHashCode(TSource obj)
            {
                return obj.Equals(default(TSource)) ? 0 : _comparer.GetHashCode(_selector(obj));
            }
        }
    }
    

    还有测试类:

    [TestClass]
    public class LINQExtensionsTest
    {
        [TestMethod]
        public void DistinctByTestDate()
        {
            var list = Enumerable.Range(0, 200).Select(i => new
            {
                Index = i,
                Date = DateTime.Today.AddDays(i%4)
            }).ToList();
    
            var distinctList = list.DistinctBy(l => l.Date).ToList();
    
            Assert.AreEqual(4, distinctList.Count);
    
            Assert.AreEqual(0, distinctList[0].Index);
            Assert.AreEqual(1, distinctList[1].Index);
            Assert.AreEqual(2, distinctList[2].Index);
            Assert.AreEqual(3, distinctList[3].Index);
    
            Assert.AreEqual(DateTime.Today, distinctList[0].Date);
            Assert.AreEqual(DateTime.Today.AddDays(1), distinctList[1].Date);
            Assert.AreEqual(DateTime.Today.AddDays(2), distinctList[2].Date);
            Assert.AreEqual(DateTime.Today.AddDays(3), distinctList[3].Date);
    
            Assert.AreEqual(200, list.Count);
        }
    
        [TestMethod]
        public void DistinctByTestInt()
        {
            var list = Enumerable.Range(0, 200).Select(i => new
            {
                Index = i % 4,
                Date = DateTime.Today.AddDays(i)
            }).ToList();
    
            var distinctList = list.DistinctBy(l => l.Index).ToList();
    
            Assert.AreEqual(4, distinctList.Count);
    
            Assert.AreEqual(0, distinctList[0].Index);
            Assert.AreEqual(1, distinctList[1].Index);
            Assert.AreEqual(2, distinctList[2].Index);
            Assert.AreEqual(3, distinctList[3].Index);
    
            Assert.AreEqual(DateTime.Today, distinctList[0].Date);
            Assert.AreEqual(DateTime.Today.AddDays(1), distinctList[1].Date);
            Assert.AreEqual(DateTime.Today.AddDays(2), distinctList[2].Date);
            Assert.AreEqual(DateTime.Today.AddDays(3), distinctList[3].Date);
    
            Assert.AreEqual(200, list.Count);
        }
    
        struct EqualityTester
        {
            public readonly int Index;
            public readonly DateTime Date;
    
            public EqualityTester(int index, DateTime date) : this()
            {
                Index = index;
                Date = date;
            }
        }
    
        [TestMethod]
        public void TestStruct()
        {
            var list = Enumerable.Range(0, 200)
                .Select(i => new EqualityTester(i, DateTime.Today.AddDays(i%4)))
                .ToList();
    
            var distinctDateList = list.DistinctBy(e => e.Date).ToList();
            var distinctIntList = list.DistinctBy(e => e.Index).ToList();
    
            Assert.AreEqual(4, distinctDateList.Count);
            Assert.AreEqual(200, distinctIntList.Count);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-03
      • 2015-06-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多