2009-06-12 8 views
24

mam te transfery obiekty:Jak szybko sprawdzić, czy dwa obiekty przesyłania danych mają takie same właściwości w języku C#?

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

nie chcę pisać

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; 
} 

Czy istnieje szybszy sposób, aby sprawdzić, czy obiekt tylko dwa obiekty te same wartości (coś, nie wymaga jednego wiersza kodu lub jednego wyrażenia logicznego dla każdej właściwości?)

Przełączanie na struktury nie jest opcją.

+0

Myślałam o tym. Moim zdaniem najlepszym sposobem na zrobienie tego byłoby narzędzie IDE. Wygląda na to, że Eclipse ma jeden - http://www.eclipsezone.com/eclipse/forums/t92613.rhtml. Zastanawiam się, czy jest coś podobnego do VS.NET? – RichardOD

+0

@RichardOD: ReSharper może to zrobić na przykład w VS.NET. – Lucero

Odpowiedz

63

Co powiesz na refleksję, być może używając wydajności Expression.Compile()? (Uwaga statyczny konstruktor tutaj gwarantuje tylko my go skompilować raz 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(); 
     } 
    } 
} 

Edycja: zaktualizowane do obsługi pól też:

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

Geniusz! Działa jak marzenie. – MatthewMartin

+2

Wow, to naprawdę słodkie. O wiele ładniejszy niż wersja z czystym odbiciem. –

+1

Dlaczego nie zainicjować treści za pomocą Expression.Constant (true), aby uniknąć w cyklu? – ASpirin

2

Niestety, będziesz musiał napisać metodę porównywania wartości pól. System.ValueType jest zbudowany, aby korzystać z odbicia i porównać wartości pól z struct, ale nawet to nie jest wskazane ze względu na niską wydajność. Najlepiej zastąpić metodę Equals, a także zaimplementować interfejs IEquatable<T> dla mocno przeciążonego przeciążenia typu wpisanego na maszynie .

Podczas pracy można równie dobrze zastąpić nadpisanie GetHashCode, uzupełniając implementację Equals. Wszystkie te kroki są uważane za dobrą praktykę.

4

Początkowo odpowiedział na (question 1831747)

Sprawdź mój MemberwiseEqualityComparer, aby sprawdzić, czy odpowiada Twoim potrzebom.

Jest bardzo łatwy w użyciu i dość wydajny. Wykorzystuje IL-emitery do generowania całej funkcji Equals i GetHashCode w pierwszym przebiegu (jeden raz dla każdego użytego typu). Będzie porównywać każde pole (prywatne lub publiczne) danego obiektu, używając domyślnego porównywalnika równości dla tego typu (EqualityComparer.Default). Używamy go w produkcji od jakiegoś czasu i wydaje się być stabilny, ale nie zostawiam żadnych gwarancji =)

Dba o wszystkie te pescy na krawędziach, o których rzadko kiedy myślisz równa się metoda (tzn. nie można porównywać własnego obiektu z wartością null, chyba że zapakowałeś go najpierw do obiektu, a wiele z problemów o mniejszej wartości).

Chciałam napisać o tym wpis na blogu, ale jeszcze się do niego nie dostałem. Kod jest trochę nieudokumentowany, ale jeśli ci się spodoba, mogę go trochę posprzątać.

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); 
} 

MemberwiseEqualityComparer jest wydane na MIT license meaining można zrobić bardzo dużo, co chcesz z nim, w tym przy użyciu go w własnych rozwiązań bez zmiany licencji Ci trochę.

+1

Jednym z możliwych ulepszeń byłoby posiadanie generatora testu równości umożliwiającego użycie atrybutów pól do wskazania, które pola zawierają * tożsamość * i które hermetyzują * wartość *. Dosyć powszechny wzorzec polega na enkapsulacji wartości klasy zmiennej przez utrzymywanie odniesienia, które nigdy nie będzie wystawione na nic, co może je zmutować. Takie pole powinno być testowane pod kątem równości wartości, nawet jeśli nazwanie "Equals" na jego typie przetestuje równość referencyjną. – supercat

3

mam rozszerzony kod Marca być pełnoprawnym realizacja IEqualityComparer dla własnych celów, i myśli może to być przydatne dla innych w przyszłości:

/// <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); 
    } 
} 
Powiązane problemy