首页 文章

如何通过Lambda或LINQ从列表中获取不同的实例

提问于
浏览
28

我有一个这样的课:

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

以及这个 class 的清单 . 我想使用.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的做法是什么?

7 回答

  • 58

    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 的解决方案慢一点 . 尽管有这个缺点,但有两个很大的优点:简单和灵活 . 通常,最好是简化优于优化,但它实际上取决于您尝试解决的问题 .

  • 9

    嗯......我可能会写一个自定义的 IEqualityComparer<T> 以便我可以使用:

    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));
            }
        }
    }
    
  • 0

    您可以使用此扩展方法:

    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)));
        }
    
  • 1

    查看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();
        }
    }
    

    您的代码段可能如下所示:

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

    这会更简单......

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

    在linq中,这更适合群体

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

    我接受了Marc的回答,修复它以使用TSource作为值类型(默认测试(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);
        }
    }
    

相关问题