2009-06-12 7 views
24

J'ai ces objets de transfert de données:Comment vérifier rapidement si deux objets de transfert de données ont des propriétés égales en C#?

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

Je ne veux pas écrire

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

Y at-il un moyen plus rapide pour tester si deux objets ayant des propriétés que possèdent les mêmes valeurs (ce qui ne nécessite pas une ligne de code ou une expression logique par propriété?)

La commutation vers des structures n'est pas une option.

+0

Je pensais à cela. Dans mon esprit, la meilleure façon de le faire serait via un outil IDE. Il semble que Eclipse a un http://www.eclipsezone.com/eclipse/forums/t92613.rhtml. Je me demande s'il y a quelque chose dans ce sens pour VS.NET? – RichardOD

+0

@RichardOD: ReSharper peut le faire dans VS.NET par exemple. – Lucero

Répondre

63

Que diriez-vous de la réflexion, en utilisant peut-être Expression.Compile() pour la performance? (Notez le cteur statique ici assure que nous recueillons seulement une fois par 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: mise à jour pour traiter les champs trop:

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

Génie! Fonctionne comme un charme. – MatthewMartin

+2

Wow, c'est vraiment gentil. Beaucoup plus agréable que la version à réflexion pure. –

+1

Pourquoi ne pas initialiser le corps avec Expression.Constant (true) pour éviter si en cycle? – ASpirin

2

Malheureusement, vous devrez écrire la méthode pour comparer les valeurs des champs. System.ValueType est construit pour utiliser la réflexion et de comparer les valeurs de champ d'un struct mais même cela est déconseillé en raison de la lenteur des performances. La meilleure chose à faire est de remplacer la méthode Equals et également implémenter l'interface IEquatable<T> pour une surcharge fortement typée Equals.

Tant que vous y êtes, vous pourriez aussi bien fournir un bon remplacement GetHashCode ainsi pour compléter l'implémentation Equals. Toutes ces étapes sont considérées comme une bonne pratique.

4

A l'origine une réponse à (question 1831747)

Consultez mon MemberwiseEqualityComparer pour voir si cela correspond à vos besoins.

C'est vraiment facile à utiliser et très efficace aussi. Il utilise IL-emit pour générer la totalité de la fonction Equals et GetHashCode lors de la première exécution (une fois pour chaque type utilisé). Il compare chaque champ (privé ou public) de l'objet donné en utilisant le comparateur d'égalité par défaut pour ce type (EqualityComparer.Default). Nous l'utilisons dans la production depuis un certain temps et il semble stable mais je ne laisserai aucune garantie =)

Il prend soin de tous ces casse-pointe que vous pensez rarement lorsque vous roulez votre propre méthode égale (c.-à-d., vous ne pouvez pas comparer votre propre objet avec null à moins que vous l'ayez d'abord mis en boîte dans un objet et que beaucoup de problèmes liés à null soient plus nombreux).

J'ai eu l'intention d'écrire un billet de blog à ce sujet, mais je n'ai pas encore eu l'occasion de le faire. Le code est un peu non documenté mais si vous l'aimez je pourrais le nettoyer un peu.

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

Le MemberwiseEqualityComparer est libéré sous le MIT license meaining vous pouvez faire à peu près tout ce que vous voulez avec elle, y compris l'utiliser dans des solutions propriétaires sans vous changer de licence un peu.

+1

Une amélioration possible serait que le générateur de test d'égalité permette l'utilisation d'attributs de champ pour indiquer quels champs encapsulent * identity * et celles qui encapsulent * value * .Un schéma assez courant consiste à encapsuler une valeur de classe mutable en maintenant une référence qui ne sera jamais exposée à quoi que ce soit qui pourrait la faire muter. appeler 'Equals' sur son type testerait l'égalité de référence. – supercat

3

J'ai étendu le code de Marc pour une mise en œuvre IEqualityComparer à part entière pour mes propres usages, et pensé que cela peut être utile à d'autres à l'avenir:

/// <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); 
    } 
} 
Questions connexes