如何通过 Lambda 或 LINQ 从列表中获取不同的实例
Posted
技术标签:
【中文标题】如何通过 Lambda 或 LINQ 从列表中获取不同的实例【英文标题】:How to get distinct instance from a list by Lambda or 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 方法?
【问题讨论】:
【参考方案1】:Marc 和 dahlbyk 的答案似乎都非常有效。我有一个更简单的解决方案。您可以使用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<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));
【讨论】:
关于代码的一个问题:ProjectionComparer 是什么? .Net 类或 LINQ 或 IEnumerable 相关类,以便您可以自定义扩展? 好的。我认为“ProjectionComparer”是您定义的任何类名,但在该类中您已将扩展方法 DistinctBy() 自定义为 IEnumerable,并且 ProjectionComparer你可以使用这个扩展方法:
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);
【讨论】:
以上是关于如何通过 Lambda 或 LINQ 从列表中获取不同的实例的主要内容,如果未能解决你的问题,请参考以下文章
如何从 PostgreSQL 查询转换为 LINQ 或 lambda 表达式
如何解决 C# 中 linq 的 lambda 表达式中的对象引用错误?