关于c#:如何通过Lambda或LINQ从列表中获取不同的实例

How to get distinct instance from a list by Lambda or LINQ

我有一个这样的班级:

1
2
3
4
class MyClass<T> {
    public string value1 { get; set; }
    public T objT { get; set; }
}

以及这个类的列表。我想使用.NET 3.5 lambda或linq按不同的值1获取MyClass的列表。我想这是可能的,而且比.NET 2.0中缓存类似列表的方法简单得多:

1
2
3
4
5
6
7
8
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的方法是什么?


马克和达尔比的答案似乎都很有效。不过,我有一个更简单的解决方案。不使用Distinct,您可以使用GroupBy。就像这样:

1
2
3
4
5
var listDistinct
    = list.GroupBy(
        i => i.value1,
        (key, group) => group.First()
    ).ToArray();

注意,我已经将两个函数传递给了GroupBy()。第一个是键选择器。第二个只从每个组中获取一个项目。从你的问题来看,我认为江户十一〔四〕是对的。如果你想的话,你可以写另一个。你可以试试看我的意思。

我用以下输入运行了一个测试:

1
2
3
4
5
6
7
8
9
10
11
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 },
};

结果是:

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

我还没有测试它的性能。我相信这个解决方案可能比使用Distinct的解决方案慢一点。尽管存在这一缺点,但有两大优势:简单性和灵活性。通常情况下,更倾向于简化而不是优化,但这实际上取决于您试图解决的问题。


隐马尔可夫模型。。。我可能会写一个自定义的IEqualityComparer,这样我就可以使用:

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

通过LINQ编写比较器……

可能有点杀伤力过大,但可重复使用,至少:

用法第一:

1
2
3
4
5
6
7
8
9
10
11
12
13
static class Program {
    static void Main() {
        var data = new[] {
            new { Foo = 1,Bar ="a
<div class="
suo-content">[collapse title=""]<ul><li>关于代码的一个问题:什么是projectioncomparer?一个.NET类、LINQ或IEnumerable相关类,以便您具有自定义扩展名?</li><li>好啊。我认为"projectioncomparer"是您定义的任何类名,但是在类中,您已经将DistinctBy()扩展方法自定义为IEnumerable,而projectioncomparer<t>是另一个助手类,对吗?ProjectionComparer是否可以是不同的名称,而不是相同的名称?</li><li>如果我想得到MyClass的Value1列表,我可以这样使用这个比较器:list<string>listValue1s=list.distinct(comparer.tolist()。select(y=>y.value1);对吗?</li><li>ProjectionComparer的名称并不重要-您可以将其称为EnumerableExtensions。projection comparer<t>之所以如此命名,是因为它通过投影提供比较器,这是基于现有值(例如,MyClass中的Value1)获取新值的常用术语。最后一个问题是:除非需要,否则不要调用tolist()。如果您不打算使用myClass<t>对象的distinct列表,那么最好像这样获取值1:IEnumerable<string>value1 s=list.select(y=>y.value1).distinct();</li><li>谢谢Dahlbyk;帮我节省了一些打字时间;-p</li><li>马克,你对JPBOchi的替代方法有什么意见吗?似乎不需要编写一个比较器扩展类,而且非常灵活。对于linq-to-object来说,这似乎已经足够好了。</li><li>它们本质上是相同的,除了我的可以用于任何对象,而不仅仅是一个特定的案例。</li></ul>[/collapse]</div><p><center>[wp_ad_camp_1]</center></p><hr><P>您可以使用此扩展方法:</P>[cc lang="csharp"]    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)));
    }


Check out Enumerable.Distinct(), which can accept an IEqualityComparer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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();
    }
}

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

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


在LINQ中,这是更先进的团队

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


这会更简单…

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


我接受了Marc的答案,将其修复为使用值类型的tsource(默认测试(tsource)而不是空值),清理了一些冗余的类型规范,并为其编写了一些测试。这是我今天使用的。感谢马克的伟大思想和实施。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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));
        }
    }
}

测试班:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
[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);
    }
}