重要提示:这不是LINQ-to-SQL问题.这是对象的LINQ.
简短的问题:
LINQ中是否有一种简单的方法可以根据对象的键属性从列表中获取不同的对象列表.
长问题:
我正在尝试对具有键作为其属性之一的对象Distinct()
列表执行操作.
class GalleryImage { public int Key { get;set; } public string Caption { get;set; } public string Filename { get; set; } public string[] Tags {g et; set; } }
我有一个Gallery
包含的对象列表GalleryImage[]
.
由于web服务的工作方式[原文如此],我有GalleryImage
对象的重复
.我认为用它Distinct()
来获得一个独特的列表是一件简单的事情.
这是我想要使用的LINQ查询:
var allImages = Galleries.SelectMany(x => x.Images); var distinctImages = allImages.Distinct(new EqualityComparer ((a, b) => a.id == b.id));
问题是这EqualityComparer
是一个抽象类.
我不想:
实现IEquatable,GalleryImage
因为它是生成的
必须编写一个单独的类来实现IEqualityComparer
,如下所示
是否有一个EqualityComparer
我缺少的具体实现?
我原本以为会有一种简单的方法可以根据键从一组中获取"不同"的对象.
(这里有两种解决方案 - 见第二种解决方案):
我的MiscUtil库有一个ProjectionEqualityComparer
类(以及两个支持类来使用类型推断).
以下是使用它的示例:
EqualityComparercomparer = ProjectionEqualityComparer .Create(x => x.id);
这是代码(删除了评论)
// Helper class for construction public static class ProjectionEqualityComparer { public static ProjectionEqualityComparerCreate (Func projection) { return new ProjectionEqualityComparer (projection); } public static ProjectionEqualityComparer Create (TSource ignored, Func projection) { return new ProjectionEqualityComparer (projection); } } public static class ProjectionEqualityComparer { public static ProjectionEqualityComparer Create (Func projection) { return new ProjectionEqualityComparer (projection); } } public class ProjectionEqualityComparer : IEqualityComparer { readonly Func projection; readonly IEqualityComparer comparer; public ProjectionEqualityComparer(Func projection) : this(projection, null) { } public ProjectionEqualityComparer( Func projection, IEqualityComparer comparer) { projection.ThrowIfNull("projection"); this.comparer = comparer ?? EqualityComparer .Default; this.projection = projection; } public bool Equals(TSource x, TSource y) { if (x == null && y == null) { return true; } if (x == null || y == null) { return false; } return comparer.Equals(projection(x), projection(y)); } public int GetHashCode(TSource obj) { if (obj == null) { throw new ArgumentNullException("obj"); } return comparer.GetHashCode(projection(obj)); } }
二解决方案
要为Distinct执行此操作,您可以DistinctBy
在MoreLINQ中使用扩展名:
public static IEnumerableDistinctBy (this IEnumerable source, Func keySelector) { return source.DistinctBy(keySelector, null); } public static IEnumerable DistinctBy (this IEnumerable source, Func keySelector, IEqualityComparer comparer) { source.ThrowIfNull("source"); keySelector.ThrowIfNull("keySelector"); return DistinctByImpl(source, keySelector, comparer); } private static IEnumerable DistinctByImpl (IEnumerable source, Func keySelector, IEqualityComparer comparer) { HashSet knownKeys = new HashSet (comparer); foreach (TSource element in source) { if (knownKeys.Add(keySelector(element))) { yield return element; } } }
在这两种情况下,ThrowIfNull
看起来像这样:
public static void ThrowIfNull(this T data, string name) where T : class { if (data == null) { throw new ArgumentNullException(name); } }
在Charlie Flowers的答案的基础上,您可以创建自己的扩展方法来执行想要的操作,该方法内部使用分组:
public static IEnumerableDistinct ( this IEnumerable seq, Func getKey) { return from item in seq group item by getKey(item) into gp select gp.First(); }
您还可以创建一个从EqualityComparer派生的通用类,但是听起来您想避免这种情况:
public class KeyEqualityComparer: IEqualityComparer { private Func GetKey { get; set; } public KeyEqualityComparer(Func getKey) { GetKey = getKey; } public bool Equals(T x, T y) { return GetKey(x).Equals(GetKey(y)); } public int GetHashCode(T obj) { return GetKey(obj).GetHashCode(); } }