2009-04-04 24 views
27

QUAN TRỌNG: Đây là KHÔNG LINQ-TO-SQL CÂU HỎI. Đây là LINQ đối tượng.Bạn có thể tạo đơn giản 'EqualityComparer <T>' bằng cách sử dụng biểu thức lambda

câu hỏi ngắn:

Có một cách đơn giản trong LINQ to đối tượng để có được một danh sách riêng biệt của đối tượng từ một danh sách dựa trên một tài sản quan trọng trên các đối tượng.

câu hỏi Long:

Tôi cố gắng để làm một hoạt động Distinct() vào một danh sách các đối tượng có một chìa khóa là một trong những tài sản của họ.

class GalleryImage { 
    public int Key { get;set; } 
    public string Caption { get;set; } 
    public string Filename { get; set; } 
    public string[] Tags {g et; set; } 
} 

Tôi có danh sách Gallery đối tượng có chứa GalleryImage[].

Do cách hoạt động của webservice [sic] Tôi có các đối tượng địa lý GalleryImage. tôi nghĩ rằng nó sẽ là một vấn đề đơn giản để sử dụng Distinct() để có được một danh sách riêng biệt.

Đây là truy vấn LINQ Tôi muốn sử dụng:

var allImages = Galleries.SelectMany(x => x.Images); 
var distinctImages = allImages.Distinct<GalleryImage>(new 
        EqualityComparer<GalleryImage>((a, b) => a.id == b.id)); 

Vấn đề là EqualityComparer là một lớp trừu tượng.

Tôi không muốn:

  • thực hiện IEquatable trên GalleryImage bởi vì nó được tạo ra
  • phải viết một lớp riêng biệt để thực hiện IEqualityComparer như shown here

Có một thực hiện cụ thể của EqualityComparer một nơi nào đó mà tôi đang mất tích?

Tôi đã nghĩ rằng sẽ có một cách dễ dàng để có được các đối tượng 'khác biệt' từ một tập hợp dựa trên khóa.

Trả lời

33

(Có hai giải pháp ở đây - xem kết thúc cho một giây):

thư viện MiscUtil của tôi có một lớp ProjectionEqualityComparer (và hai lớp hỗ trợ để tận dụng suy luận kiểu).

Dưới đây là một ví dụ của việc sử dụng nó:

EqualityComparer<GalleryImage> comparer = 
    ProjectionEqualityComparer<GalleryImage>.Create(x => x.id); 

Dưới đây là đoạn code (nhận xét bị xóa)

// Helper class for construction 
public static class ProjectionEqualityComparer 
{ 
    public static ProjectionEqualityComparer<TSource, TKey> 
     Create<TSource, TKey>(Func<TSource, TKey> projection) 
    { 
     return new ProjectionEqualityComparer<TSource, TKey>(projection); 
    } 

    public static ProjectionEqualityComparer<TSource, TKey> 
     Create<TSource, TKey> (TSource ignored, 
           Func<TSource, TKey> projection) 
    { 
     return new ProjectionEqualityComparer<TSource, TKey>(projection); 
    } 
} 

public static class ProjectionEqualityComparer<TSource> 
{ 
    public static ProjectionEqualityComparer<TSource, TKey> 
     Create<TKey>(Func<TSource, TKey> projection) 
    { 
     return new ProjectionEqualityComparer<TSource, TKey>(projection); 
    } 
} 

public class ProjectionEqualityComparer<TSource, TKey> 
    : IEqualityComparer<TSource> 
{ 
    readonly Func<TSource, TKey> projection; 
    readonly IEqualityComparer<TKey> comparer; 

    public ProjectionEqualityComparer(Func<TSource, TKey> projection) 
     : this(projection, null) 
    { 
    } 

    public ProjectionEqualityComparer(
     Func<TSource, TKey> projection, 
     IEqualityComparer<TKey> comparer) 
    { 
     projection.ThrowIfNull("projection"); 
     this.comparer = comparer ?? EqualityComparer<TKey>.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)); 
    } 
} 

giải pháp thứ hai

Để làm điều này chỉ dành riêng cho biệt, bạn có thể sử dụng tiện ích mở rộng DistinctBy trong MoreLINQ:

public static IEnumerable<TSource> DistinctBy<TSource, TKey> 
     (this IEnumerable<TSource> source, 
     Func<TSource, TKey> keySelector) 
    { 
     return source.DistinctBy(keySelector, null); 
    } 

    public static IEnumerable<TSource> DistinctBy<TSource, TKey> 
     (this IEnumerable<TSource> source, 
     Func<TSource, TKey> keySelector, 
     IEqualityComparer<TKey> comparer) 
    { 
     source.ThrowIfNull("source"); 
     keySelector.ThrowIfNull("keySelector"); 
     return DistinctByImpl(source, keySelector, comparer); 
    } 

    private static IEnumerable<TSource> DistinctByImpl<TSource, TKey> 
     (IEnumerable<TSource> source, 
     Func<TSource, TKey> keySelector, 
     IEqualityComparer<TKey> comparer) 
    { 
     HashSet<TKey> knownKeys = new HashSet<TKey>(comparer); 
     foreach (TSource element in source) 
     { 
      if (knownKeys.Add(keySelector(element))) 
      { 
       yield return element; 
      } 
     } 
    } 

Trong cả hai trường hợp, ThrowIfNull trông như thế này:

public static void ThrowIfNull<T>(this T data, string name) where T : class 
{ 
    if (data == null) 
    { 
     throw new ArgumentNullException(name); 
    } 
} 
+0

Thông tin tuyệt vời! Cảm ơn! –

+0

Điểm của phương thức Create tĩnh thứ hai với tham số 'TSource ignore' là gì? public static ProjectionEqualityComparer Tạo (TSource bị bỏ qua, Func phép chiếu) { trả về ProjectionEqualityComparer (chiếu); } – flesh

+1

@flesh: Nó cho phép suy luận kiểu để khởi động khi bạn không thể chỉ định loại rõ ràng - ví dụ: cho các loại ẩn danh. –

2

Bạn có thể nhóm theo giá trị khóa và sau đó chọn mục trên cùng từ mỗi nhóm. bạn có muốn công việc kia?

+0

vâng tôi chỉ nhìn vào thực tế - thông qua ToLookup(). có thể không hiệu quả và chậm nhưng ok cho nhiệm vụ này. gửi tuyên bố của tôi ở trên/dưới đây –

3

Đây là cách tốt nhất để tôi có thể giải quyết vấn đề trong tay. Vẫn còn tò mò cho dù có một cách tốt đẹp để tạo ra một EqualityComparer trên bay mặc dù.

Galleries.SelectMany(x => x.Images).ToLookup(x => x.id).Select(x => x.First()); 

Tạo bảng tra cứu và mất 'hàng đầu' từ mỗi một

Lưu ý: đây là giống như @charlie gợi ý nhưng sử dụng ILookup - mà tôi nghĩ là những gì một nhóm phải anyway.

+0

Tôi đồng ý rằng nó cảm thấy như khuôn khổ thiếu một cái gì đó. Tôi không biết nếu nó là IEqualityComparer mặc dù ... nó thực sự cần cả hai phương pháp. Nó cảm thấy như có phải là một cách dễ dàng hơn bằng cách sử dụng Distinct: một ghi đè mà có một vị ngữ. –

+0

Không phải là vị từ. Tôi có nghĩa là một ghi đè của Distinct mà sẽ mất T và cho phép bạn chọn đối tượng mà bạn muốn sử dụng cho sự khác biệt. –

+0

@charlie - đúng, đó là những gì tôi thực sự nghĩ rằng tôi sẽ nhận được với Distinct hiện tại (..). Tôi chỉ chưa bao giờ sử dụng nó trong bối cảnh này trước đây, và tất nhiên nó bật ra không được những gì tôi mong đợi –

2

Điều gì về việc vứt bỏ IEqualityComparer lớp học chung?

public class ThrowAwayEqualityComparer<T> : IEqualityComparer<T> 
{ 
    Func<T, T, bool> comparer; 

    public ThrowAwayEqualityComparer<T>(Func<T, T, bool> comparer) 
    { 
    this.comparer = comparer; 
    } 

    public bool Equals(T a, T b) 
    { 
    return comparer(a, b); 
    } 

    public int GetHashCode(T a) 
    { 
    return a.GetHashCode(); 
    } 
} 

Bây giờ bạn có thể sử dụng Distinct.

var distinctImages = allImages.Distinct(
    new ThrowAwayEqualityComparer<GalleryImage>((a, b) => a.Key == b.Key)); 

Bạn có thể có thể nhận được ngay với các <GalleryImage>, nhưng tôi không chắc chắn nếu trình biên dịch có thể suy ra các loại (không có quyền truy cập vào nó ngay bây giờ.)

Và trong một thêm phương pháp khuyến nông:

public static class IEnumerableExtensions 
{ 
    public static IEnumerable<TValue> Distinct<TValue>(this IEnumerable<TValue> @this, Func<TValue, TValue, bool> comparer) 
    { 
    return @this.Distinct(new ThrowAwayEqualityComparer<TValue>(comparer); 
    } 

    private class ThrowAwayEqualityComparer... 
} 
+0

Khá tốt. Sau đó, bạn cũng có thể thực hiện ghi đè của Distinct mà tôi mong muốn. –

+0

Có, bạn có thể dễ dàng làm điều đó và có được những gì bạn muốn. – Samuel

+0

Nhưng không phải bạn vẫn đang triển khai IEqualityComparer . Có vẻ như bạn không muốn làm điều đó. –

4

xây dựng trên Charlie Flowers' câu trả lời, bạn có thể tạo phương pháp khuyến nông của riêng bạn để làm những gì bạn muốn mà trong nội bộ sử dụng nhóm:

public static IEnumerable<T> Distinct<T, U>(
     this IEnumerable<T> seq, Func<T, U> getKey) 
    { 
     return 
      from item in seq 
      group item by getKey(item) into gp 
      select gp.First(); 
    } 

Bạn cũng có thể tạo ra một lớp chung bắt nguồn từ EqualityComparer, nhưng có vẻ như bạn muốn tránh điều này:

public class KeyEqualityComparer<T,U> : IEqualityComparer<T> 
    { 
     private Func<T,U> GetKey { get; set; } 

     public KeyEqualityComparer(Func<T,U> 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(); 
     } 
    } 
1

Dưới đây là một bài viết thú vị mà kéo dài LINQ cho mục đích này ... http://www.singingeels.com/Articles/Extending_LINQ__Specifying_a_Property_in_the_Distinct_Function.aspx

Distinct mặc định so sánh các đối tượng dựa trên mã băm của chúng - để dễ dàng làm cho đối tượng của bạn hoạt động với Distinct, bạn có thể ghi đè phương thức GetHashcode .. nhưng bạn đã đề cập rằng bạn đang truy xuất đối tượng của mình từ một dịch vụ web, vì vậy bạn không thể để làm điều đó trong trường hợp này.

0

thực hiện IEquatable trên GalleryImage bởi vì nó được tạo ra

Một cách tiếp cận khác nhau sẽ được tạo ra GalleryImage như một lớp học phần, và sau đó có một tập tin khác với thừa kế và IEquatable, Equals, GetHash thực hiện.

0

Ý tưởng này đang được tranh luận here và trong khi tôi hy vọng điều này.đội ngũ cốt lõi NET thông qua một phương pháp để tạo ra IEqualityComparer<T> s từ lambda, tôi muốn đề nghị bạn để làm hài lòng bỏ phiếu và nhận xét về ý tưởng đó, và sử dụng như sau:

Cách sử dụng:

IEqualityComparer<Contact> comp1 = EqualityComparerImpl<Contact>.Create(c => c.Name); 
var comp2 = EqualityComparerImpl<Contact>.Create(c => c.Name, c => c.Age); 

class Contact { public Name { get; set; } public Age { get; set; } } 

Code:

public class EqualityComparerImpl<T> : IEqualityComparer<T> 
{ 
    public static EqualityComparerImpl<T> Create(
    params Expression<Func<T, object>>[] properties) => 
    new EqualityComparerImpl<T>(properties); 

    PropertyInfo[] _properties; 
    EqualityComparerImpl(Expression<Func<T, object>>[] properties) 
    { 
    if (properties == null) 
     throw new ArgumentNullException(nameof(properties)); 

    if (properties.Length == 0) 
     throw new ArgumentOutOfRangeException(nameof(properties)); 

    var length = properties.Length; 
    var extractions = new PropertyInfo[length]; 
    for (int i = 0; i < length; i++) 
    { 
     var property = properties[i]; 
     extractions[i] = ExtractProperty(property); 
    } 
    _properties = extractions; 
    } 

    public bool Equals(T x, T y) 
    { 
    if (ReferenceEquals(x, y)) 
     //covers both are null 
     return true; 
    if (x == null || y == null) 
     return false; 
    var len = _properties.Length; 
    for (int i = 0; i < _properties.Length; i++) 
    { 
     var property = _properties[i]; 
     if (!Equals(property.GetValue(x), property.GetValue(y))) 
     return false; 
    } 
    return true; 
    } 

    public int GetHashCode(T obj) 
    { 
    if (obj == null) 
     return 0; 

    var hashes = _properties 
     .Select(pi => pi.GetValue(obj)?.GetHashCode() ?? 0).ToArray(); 
    return Combine(hashes); 
    } 

    static int Combine(int[] hashes) 
    { 
    int result = 0; 
    foreach (var hash in hashes) 
    { 
     uint rol5 = ((uint)result << 5) | ((uint)result >> 27); 
     result = ((int)rol5 + result)^hash; 
    } 
    return result; 
    } 

    static PropertyInfo ExtractProperty(Expression<Func<T, object>> property) 
    { 
    if (property.NodeType != ExpressionType.Lambda) 
     throwEx(); 

    var body = property.Body; 
    if (body.NodeType == ExpressionType.Convert) 
     if (body is UnaryExpression unary) 
     body = unary.Operand; 
     else 
     throwEx(); 

    if (!(body is MemberExpression member)) 
     throwEx(); 

    if (!(member.Member is PropertyInfo pi)) 
     throwEx(); 

    return pi; 

    void throwEx() => 
     throw new NotSupportedException($"The expression '{property}' isn't supported."); 
    } 
} 
Các vấn đề liên quan