2009-06-12 9 views
24

Tôi có những vật thể truyền dữ liệu:Làm thế nào để nhanh chóng kiểm tra xem hai đối tượng chuyển dữ liệu có thuộc tính bằng nhau trong C# không?

public class Report 
{ 
    public int Id { get; set; } 
    public int ProjectId { get; set; } 
    //and so on for many, many properties. 
} 

Tôi không muốn viết

public bool areEqual(Report a, Report b) 
{ 
    if (a.Id != b.Id) return false; 
    if (a.ProjectId != b.ProjectId) return false; 
    //Repeat ad nauseum 
    return true; 
} 

Có cách nào nhanh hơn để kiểm tra xem hai đối tượng chỉ với thuộc tính có cùng giá trị (cái gì đó không yêu cầu một dòng mã hoặc một biểu thức logic cho mỗi thuộc tính?)

Việc chuyển sang cấu trúc không phải là một tùy chọn.

+0

Tôi đã suy nghĩ về việc này. Trong tâm trí của tôi, cách tốt nhất để làm điều này sẽ là thông qua một công cụ IDE. Có vẻ như Eclipse có một http://www.eclipsezone.com/eclipse/forums/t92613.rhtml. Tôi tự hỏi nếu có gì đó dọc theo những dòng cho VS.NET? – RichardOD

+0

@RichardOD: ReSharper có thể làm điều này trong VS.NET chẳng hạn. – Lucero

Trả lời

63

Làm thế nào về một số phản ánh, có thể sử dụng Expression.Compile() để thực hiện? (Lưu ý ctor tĩnh ở đây đảm bảo chúng tôi chỉ biên dịch nó một lần mỗi T):

using System; 
using System.Linq.Expressions; 

public class Report { 
    public int Id { get; set; } 
    public int ProjectId { get; set; } 
    static void Main() { 
     Report a = new Report { Id = 1, ProjectId = 13 }, 
      b = new Report { Id = 1, ProjectId = 13 }, 
      c = new Report { Id = 1, ProjectId = 12 }; 
     Console.WriteLine(PropertyCompare.Equal(a, b)); 
     Console.WriteLine(PropertyCompare.Equal(a, c)); 
    } 
} 
static class PropertyCompare { 
    public static bool Equal<T>(T x, T y) { 
     return Cache<T>.Compare(x, y); 
    } 
    static class Cache<T> { 
     internal static readonly Func<T, T, bool> Compare; 
     static Cache() { 
      var props = typeof(T).GetProperties(); 
      if (props.Length == 0) { 
       Compare = delegate { return true; }; 
       return; 
      } 
      var x = Expression.Parameter(typeof(T), "x"); 
      var y = Expression.Parameter(typeof(T), "y"); 

      Expression body = null; 
      for (int i = 0; i < props.Length; i++) { 
       var propEqual = Expression.Equal(
        Expression.Property(x, props[i]), 
        Expression.Property(y, props[i])); 
       if (body == null) { 
        body = propEqual; 
       } else { 
        body = Expression.AndAlso(body, propEqual); 
       } 
      } 
      Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y) 
          .Compile(); 
     } 
    } 
} 

Edit: được cập nhật để xử lý các lĩnh vực quá:

static class MemberCompare 
{ 
    public static bool Equal<T>(T x, T y) 
    { 
     return Cache<T>.Compare(x, y); 
    } 
    static class Cache<T> 
    { 
     internal static readonly Func<T, T, bool> Compare; 
     static Cache() 
     { 
      var members = typeof(T).GetProperties(
       BindingFlags.Instance | BindingFlags.Public) 
       .Cast<MemberInfo>().Concat(typeof(T).GetFields(
       BindingFlags.Instance | BindingFlags.Public) 
       .Cast<MemberInfo>()); 
      var x = Expression.Parameter(typeof(T), "x"); 
      var y = Expression.Parameter(typeof(T), "y"); 

      Expression body = null; 
      foreach(var member in members) 
      { 
       Expression memberEqual; 
       switch (member.MemberType) 
       { 
        case MemberTypes.Field: 
         memberEqual = Expression.Equal(
          Expression.Field(x, (FieldInfo)member), 
          Expression.Field(y, (FieldInfo)member)); 
         break; 
        case MemberTypes.Property: 
         memberEqual = Expression.Equal(
          Expression.Property(x, (PropertyInfo)member), 
          Expression.Property(y, (PropertyInfo)member)); 
         break; 
        default: 
         throw new NotSupportedException(
          member.MemberType.ToString()); 
       } 
       if (body == null) 
       { 
        body = memberEqual; 
       } 
       else 
       { 
        body = Expression.AndAlso(body, memberEqual); 
       } 
      } 
      if (body == null) 
      { 
       Compare = delegate { return true; }; 
      } 
      else 
      { 
       Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y) 
           .Compile(); 
      } 
     } 
    } 
} 
+2

Genius! Làm việc như một say mê. – MatthewMartin

+2

Chà, thật là ngọt ngào. Đẹp hơn nhiều so với phiên bản phản chiếu thuần túy. –

+1

Tại sao bạn không khởi tạo cơ thể bằng Expression.Constant (true) để tránh nếu trong chu kỳ? – ASpirin

2

Thật không may là bạn sẽ phải viết phương pháp để so sánh các giá trị trường. System.ValueType được xây dựng để sử dụng phản chiếu và so sánh các giá trị trường của struct nhưng ngay cả điều này cũng không thể thực hiện được do hiệu suất chậm. Điều tốt nhất cần làm là ghi đè phương thức Equals và cũng triển khai giao diện IEquatable<T> cho quá tải Equals bị đánh mạnh.

Khi bạn đang ở đó, bạn cũng có thể cung cấp ghi đè tốt GetHashCode để bổ sung cho việc triển khai Equals. Tất cả các bước này được coi là thực hành tốt.

4

Được trả lời lúc đầu (question 1831747)

Kiểm tra MemberwiseEqualityComparer của tôi để xem nó có phù hợp với nhu cầu của bạn hay không.

Nó thực sự dễ sử dụng và khá hiệu quả. Nó sử dụng IL-emit để tạo ra toàn bộ hàm Equals và GetHashCode trong lần chạy đầu tiên (một lần cho mỗi loại được sử dụng). Nó sẽ so sánh từng trường (riêng tư hoặc công khai) của đối tượng đã cho bằng cách sử dụng trình so sánh bình đẳng mặc định cho kiểu đó (EqualityComparer.Default). Chúng tôi đã sử dụng nó trong sản xuất một thời gian và nó có vẻ ổn định nhưng tôi sẽ không bảo đảm =)

Nó sẽ chăm sóc tất cả những trường hợp cạnh mà bạn hiếm khi nghĩ đến khi bạn tự mình lăn bằng phương thức (ví dụ, bạn không thể so sánh đối tượng của riêng bạn với null trừ khi bạn đã đóng nó trong một đối tượng đầu tiên và rất nhiều các vấn đề liên quan đến null).

Tôi đã có ý định viết một bài đăng trên blog về nó nhưng chưa tham gia. Mã này là một chút không có giấy tờ nhưng nếu bạn thích nó tôi có thể làm sạch nó lên một chút.

public override int GetHashCode() 
{ 
    return MemberwiseEqualityComparer<Foo>.Default.GetHashCode(this); 
} 

public override bool Equals(object obj) 
{ 
    if (obj == null) 
     return false; 

    return Equals(obj as Foo); 
} 

public override bool Equals(Foo other) 
{ 
    return MemberwiseEqualityComparer<Foo>.Default.Equals(this, other); 
} 

Các MemberwiseEqualityComparer được phát hành theo MIT license meaining bạn có thể làm được khá nhiều gì bạn muốn với nó, trong đó có sử dụng nó trong các giải pháp độc quyền mà không thay đổi bạn cấp phép một chút.

+1

Một sự cải tiến có thể là để có bộ tạo kiểm tra bình đẳng cho phép sử dụng các thuộc tính của trường để biểu thị trường nào đóng gói Một định dạng khá phổ biến là đóng gói một giá trị có thể thay đổi được bằng cách giữ một tham chiếu mà sẽ không bao giờ được tiếp xúc với bất cứ thứ gì có thể làm biến đổi nó. gọi 'Equals' trên loại của nó sẽ kiểm tra tính bình đẳng tham chiếu. – supercat

3

tôi đã mở rộng đang Marc là một thực hiện IEqualityComparer đầy đủ chính thức cho mục đích riêng của tôi, và nghĩ rằng điều này có thể có ích cho những người khác trong tương lai:

/// <summary> 
/// An <see cref="IEqualityComparer{T}"/> that compares the values of each public property. 
/// </summary> 
/// <typeparam name="T"> The type to compare. </typeparam> 
public class PropertyEqualityComparer<T> : IEqualityComparer<T> 
{ 
    // http://stackoverflow.com/questions/986572/hows-to-quick-check-if-data-transfer-two-objects-have-equal-properties-in-c/986617#986617 

    static class EqualityCache 
    { 
     internal static readonly Func<T, T, bool> Compare; 
     static EqualityCache() 
     { 
      var props = typeof(T).GetProperties(); 
      if (props.Length == 0) 
      { 
       Compare = delegate { return true; }; 
       return; 
      } 
      var x = Expression.Parameter(typeof(T), "x"); 
      var y = Expression.Parameter(typeof(T), "y"); 

      Expression body = null; 
      for (int i = 0; i < props.Length; i++) 
      { 
       var propEqual = Expression.Equal(
        Expression.Property(x, props[i]), 
        Expression.Property(y, props[i])); 
       if (body == null) 
       { 
        body = propEqual; 
       } 
       else 
       { 
        body = Expression.AndAlso(body, propEqual); 
       } 
      } 
      Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y).Compile(); 
     } 
    } 

    /// <inheritdoc/> 
    public bool Equals(T x, T y) 
    { 
     return EqualityCache.Compare(x, y); 
    } 

    static class HashCodeCache 
    { 
     internal static readonly Func<T, int> Hasher; 
     static HashCodeCache() 
     { 
      var props = typeof(T).GetProperties(); 
      if (props.Length == 0) 
      { 
       Hasher = delegate { return 0; }; 
       return; 
      } 
      var x = Expression.Parameter(typeof(T), "x"); 

      Expression body = null; 
      for (int i = 0; i < props.Length; i++) 
      { 
       var prop = Expression.Property(x, props[i]); 
       var type = props[i].PropertyType; 
       var isNull = type.IsValueType ? (Expression)Expression.Constant(false, typeof(bool)) : Expression.Equal(prop, Expression.Constant(null, type)); 
       var hashCodeFunc = type.GetMethod("GetHashCode", BindingFlags.Instance | BindingFlags.Public); 
       var getHashCode = Expression.Call(prop, hashCodeFunc); 
       var hashCode = Expression.Condition(isNull, Expression.Constant(0, typeof(int)), getHashCode); 

       if (body == null) 
       { 
        body = hashCode; 
       } 
       else 
       { 
        body = Expression.ExclusiveOr(Expression.Multiply(body, Expression.Constant(typeof(T).AssemblyQualifiedName.GetHashCode(), typeof(int))), hashCode); 
       } 
      } 
      Hasher = Expression.Lambda<Func<T, int>>(body, x).Compile(); 
     } 
    } 

    /// <inheritdoc/> 
    public int GetHashCode(T obj) 
    { 
     return HashCodeCache.Hasher(obj); 
    } 
}