2009-06-12 13 views
24

tengo estos objetos de transferencia de datos:¿Cómo verificar rápidamente si dos objetos de transferencia de datos tienen las mismas propiedades en C#?

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

yo no quiero escribir

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

¿Hay una manera más rápida para probar si dos objetos con propiedades únicas tienen los mismos valores (algo que no requiere una línea de código o una expresión lógica por propiedad?)

Cambiar a las estructuras no es una opción.

+0

Estaba pensando en esto. En mi opinión, la mejor manera de hacerlo sería a través de una herramienta IDE. Parece que Eclipse tiene uno: http://www.eclipsezone.com/eclipse/forums/t92613.rhtml. Me pregunto si hay algo en ese sentido para VS.NET. – RichardOD

+0

@RichardOD: ReSharper puede hacer esto en VS.NET, por ejemplo. – Lucero

Respuesta

63

¿Qué tal un poco de reflexión, tal vez usando Expression.Compile() para el rendimiento? (Nótese el ctor estática aquí asegura que sólo compila una vez por 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(); 
     } 
    } 
} 

Editar: actualizado para manejar campos también:

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! Funciona de maravilla. – MatthewMartin

+2

Guau, eso es realmente dulce. Mucho mejor que la versión de reflexión pura. –

+1

¿Por qué no inicializar el cuerpo con Expression.Constant (true) para evitar si está en ciclo? – ASpirin

2

Desafortunadamente, tendrá que escribir el método para comparar los valores de campo. System.ValueType está diseñado para usar la reflexión y comparar los valores de campo de struct, pero incluso esto no es aconsejable debido al bajo rendimiento. Lo mejor que puede hacer es anular el método Equals y también implementar la interfaz IEquatable<T> para una sobrecarga fuertemente tipada Equals.

Mientras lo hace, también puede proporcionar una buena anulación GetHashCode para complementar la implementación Equals. Todos estos pasos se consideran buenas prácticas.

4

respondido originalmente en (question 1831747)

Eche un vistazo a mi MemberwiseEqualityComparer para ver si se ajusta a sus necesidades.

Es muy fácil de usar y bastante eficiente también. Utiliza IL-emit para generar toda la función Equals y GetHashCode en la primera ejecución (una para cada tipo utilizado). Comparará cada campo (privado o público) del objeto dado utilizando el comparador de igualdad predeterminado para ese tipo (EqualityComparer.Default). Lo hemos usado en producción por un tiempo y parece estable, pero no dejaré garantías =)

Cuida todos esos casos pesados ​​que rara vez piensas cuando estás rodando tu propio es igual al método (es decir, no puedes comparar tu propio objeto con nulo a menos que lo hayas encuadrado primero en un objeto y muchos más problemas relacionados con el nulo).

He tenido la intención de escribir una publicación en un blog al respecto, pero aún no me he enterado. El código está un poco indocumentado pero, si te gusta, podría limpiarlo un poco.

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

El MemberwiseEqualityComparer es liberado bajo la MIT license meaining que puede hacer casi todo lo que quiera con él, incluyendo su uso en soluciones propietarias sin cambiar un poco que la concesión de licencias.

+1

Una posible mejora sería que el generador de pruebas de igualdad permita el uso de atributos de campo para indicar qué campos encapsulan * identity * y cuáles encapsulan * value *. Un patrón bastante común es encapsular un valor de clase mutable manteniendo una referencia que nunca estará expuesta a nada que pueda mutarla. Tal campo debe ser probado para la igualdad de valores a pesar de que llamar a 'Igual' en su tipo probaría la igualdad de referencia. – supercat

3

que hemos ampliado código de Marc ser una aplicación de pleno derecho IEqualityComparer para mi propio uso, y pensaron que esto puede ser útil para otros en el futuro:

/// <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); 
    } 
} 
Cuestiones relacionadas