2009-06-05 24 views
114

Tengo dos objetos del mismo tipo, y quiero recorrer las propiedades públicas de cada uno de ellos y alertar al usuario sobre cuáles propiedades no coinciden.Loop Through An Objects Properties En C#

¿Es posible hacer esto sin saber qué propiedades contiene el objeto?

+0

Creo que esto le ayudará a ... [Iterar a través de propiedades y valores] (http://stackoverflow.com/questions/9724247/iterate-through-properties -and-values-of-a-object-returned-via-a-linq-query-on) –

+0

Creo que esto te ayudará. [Iterar a través de las propiedades y valores de un objeto] (http://stackoverflow.com/questions/9724247/iterate-through-properties-and-values-of-an-object-returned-via-a-linq-query-on) –

Respuesta

161

Sí, con la reflexión - suponiendo que cada tipo de propiedad implementa Equals adecuadamente. Una alternativa sería usar ReflectiveEquals recursivamente para todos los tipos, excepto algunos conocidos, pero eso se vuelve complicado.

public bool ReflectiveEquals(object first, object second) 
{ 
    if (first == null && second == null) 
    { 
     return true; 
    } 
    if (first == null || second == null) 
    { 
     return false; 
    } 
    Type firstType = first.GetType(); 
    if (second.GetType() != firstType) 
    { 
     return false; // Or throw an exception 
    } 
    // This will only use public properties. Is that enough? 
    foreach (PropertyInfo propertyInfo in firstType.GetProperties()) 
    { 
     if (propertyInfo.CanRead) 
     { 
      object firstValue = propertyInfo.GetValue(first, null); 
      object secondValue = propertyInfo.GetValue(second, null); 
      if (!object.Equals(firstValue, secondValue)) 
      { 
       return false; 
      } 
     } 
    } 
    return true; 
} 
+1

Se agregó un enfoque más moderno a esto a continuación ... –

+0

¿Sería posible utilizar recursividad con este método y comparar todas las colecciones que el objeto puede tener? p.ej. Object1 -> List (of School) -> List (of Classes) -> List (of Students) –

+0

@PeterPitLock: Bueno, es probable que desee un manejo diferente para las colecciones, simplemente comparar propiedades en las listas no funcionaría bien. –

28

Seguro que puedes con la reflexión. Aquí está el código para tomar las propiedades de un tipo dado.

var info = typeof(SomeType).GetProperties(); 

Si puede dar más información sobre lo que está comparando acerca de las propiedades, podemos reunir un algoritmo de diferenciación básico. Este código de intstance diferirá en los nombres

public bool AreDifferent(Type t1, Type t2) { 
    var list1 = t1.GetProperties().OrderBy(x => x.Name).Select(x => x.Name); 
    var list2 = t2.GetProperties().OrderBy(x => x.Name).Select(x => x.Name); 
    return list1.SequenceEqual(list2); 
} 
+0

Dale un golpe. Drats. –

+0

Creo que se refería a dos objetos del mismo tipo donde los * valores * no coinciden. – BFree

+0

@JaredPar: Diffing no funciona. Los objetos PropertyInfo ciertamente no son idénticos a menos que el tipo en sí sea ... –

3

Sí. Use Reflection. Con la reflexión, que puede hacer cosas como:

//given object of some type 
object myObjectFromSomewhere; 
Type myObjOriginalType = myObjectFromSomewhere.GetType(); 
PropertyInfo[] myProps = myObjOriginalType.GetProperties(); 

Y a continuación, puede utilizar las clases PropertyInfo resultantes de comparar todo tipo de cosas.

6

Sé que esto es algo excesivo, pero aquí está mi clase ObjectComparer utilizo para este mismo propósito:

/// <summary> 
/// Utility class for comparing objects. 
/// </summary> 
public static class ObjectComparer 
{ 
    /// <summary> 
    /// Compares the public properties of any 2 objects and determines if the properties of each 
    /// all contain the same value. 
    /// <para> 
    /// In cases where object1 and object2 are of different Types (both being derived from Type T) 
    /// we will cast both objects down to the base Type T to ensure the property comparison is only 
    /// completed on COMMON properties. 
    /// (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo -- 
    /// both objects will be cast to Foo for comparison) 
    /// </para> 
    /// </summary> 
    /// <typeparam name="T">Any class with public properties.</typeparam> 
    /// <param name="object1">Object to compare to object2.</param> 
    /// <param name="object2">Object to compare to object1.</param> 
    /// <param name="propertyInfoList">A List of <see cref="PropertyInfo"/> objects that contain data on the properties 
    /// from object1 that are not equal to the corresponding properties of object2.</param> 
    /// <returns>A boolean value indicating whether or not the properties of each object match.</returns> 
    public static bool GetDifferentProperties<T> (T object1 , T object2 , out List<PropertyInfo> propertyInfoList) 
     where T : class 
    { 
     return GetDifferentProperties<T>(object1 , object2 , null , out propertyInfoList); 
    } 

    /// <summary> 
    /// Compares the public properties of any 2 objects and determines if the properties of each 
    /// all contain the same value. 
    /// <para> 
    /// In cases where object1 and object2 are of different Types (both being derived from Type T) 
    /// we will cast both objects down to the base Type T to ensure the property comparison is only 
    /// completed on COMMON properties. 
    /// (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo -- 
    /// both objects will be cast to Foo for comparison) 
    /// </para> 
    /// </summary> 
    /// <typeparam name="T">Any class with public properties.</typeparam> 
    /// <param name="object1">Object to compare to object2.</param> 
    /// <param name="object2">Object to compare to object1.</param> 
    /// <param name="ignoredProperties">A list of <see cref="PropertyInfo"/> objects 
    /// to ignore when completing the comparison.</param> 
    /// <param name="propertyInfoList">A List of <see cref="PropertyInfo"/> objects that contain data on the properties 
    /// from object1 that are not equal to the corresponding properties of object2.</param> 
    /// <returns>A boolean value indicating whether or not the properties of each object match.</returns> 
    public static bool GetDifferentProperties<T> (T object1 , T object2 , List<PropertyInfo> ignoredProperties , out List<PropertyInfo> propertyInfoList) 
     where T : class 
    { 
     propertyInfoList = new List<PropertyInfo>(); 

     // If either object is null, we can't compare anything 
     if (object1 == null || object2 == null) 
     { 
      return false; 
     } 

     Type object1Type = object1.GetType(); 
     Type object2Type = object2.GetType(); 

     // In cases where object1 and object2 are of different Types (both being derived from Type T) 
     // we will cast both objects down to the base Type T to ensure the property comparison is only 
     // completed on COMMON properties. 
     // (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo -- 
     // both objects will be cast to Foo for comparison) 
     if (object1Type != object2Type) 
     { 
      object1Type = typeof (T); 
      object2Type = typeof (T); 
     } 

     // Remove any properties to be ignored 
     List<PropertyInfo> comparisonProps = 
      RemoveProperties(object1Type.GetProperties() , ignoredProperties); 

     foreach (PropertyInfo object1Prop in comparisonProps) 
     { 
      Type propertyType = null; 
      object object1PropValue = null; 
      object object2PropValue = null; 

      // Rule out an attempt to check against a property which requires 
      // an index, such as one accessed via this[] 
      if (object1Prop.GetIndexParameters().GetLength(0) == 0) 
      { 
       // Get the value of each property 
       object1PropValue = object1Prop.GetValue(object1 , null); 
       object2PropValue = object2Type.GetProperty(object1Prop.Name).GetValue(object2 , null); 

       // As we are comparing 2 objects of the same type we know 
       // that they both have the same properties, so grab the 
       // first non-null value 
       if (object1PropValue != null) 
        propertyType = object1PropValue.GetType().GetInterface("IComparable"); 

       if (propertyType == null) 
        if (object2PropValue != null) 
         propertyType = object2PropValue.GetType().GetInterface("IComparable"); 
      } 

      // If both objects have null values or were indexed properties, don't continue 
      if (propertyType != null) 
      { 
       // If one property value is null and the other is not null, 
       // they aren't equal; this is done here as a native CompareTo 
       // won't work with a null value as the target 
       if (object1PropValue == null || object2PropValue == null) 
       { 
        propertyInfoList.Add(object1Prop); 
       } 
       else 
       { 
        // Use the native CompareTo method 
        MethodInfo nativeCompare = propertyType.GetMethod("CompareTo"); 

        // Sanity Check: 
        // If we don't have a native CompareTo OR both values are null, we can't compare; 
        // hence, we can't confirm the values differ... just go to the next property 
        if (nativeCompare != null) 
        { 
         // Return the native CompareTo result 
         bool equal = (0 == (int) (nativeCompare.Invoke(object1PropValue , new object[] {object2PropValue}))); 

         if (!equal) 
         { 
          propertyInfoList.Add(object1Prop); 
         } 
        } 
       } 
      } 
     } 
     return propertyInfoList.Count == 0; 
    } 

    /// <summary> 
    /// Compares the public properties of any 2 objects and determines if the properties of each 
    /// all contain the same value. 
    /// <para> 
    /// In cases where object1 and object2 are of different Types (both being derived from Type T) 
    /// we will cast both objects down to the base Type T to ensure the property comparison is only 
    /// completed on COMMON properties. 
    /// (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo -- 
    /// both objects will be cast to Foo for comparison) 
    /// </para> 
    /// </summary> 
    /// <typeparam name="T">Any class with public properties.</typeparam> 
    /// <param name="object1">Object to compare to object2.</param> 
    /// <param name="object2">Object to compare to object1.</param> 
    /// <returns>A boolean value indicating whether or not the properties of each object match.</returns> 
    public static bool HasSamePropertyValues<T> (T object1 , T object2) 
     where T : class 
    { 
     return HasSamePropertyValues<T>(object1 , object2 , null); 
    } 

    /// <summary> 
    /// Compares the public properties of any 2 objects and determines if the properties of each 
    /// all contain the same value. 
    /// <para> 
    /// In cases where object1 and object2 are of different Types (both being derived from Type T) 
    /// we will cast both objects down to the base Type T to ensure the property comparison is only 
    /// completed on COMMON properties. 
    /// (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo -- 
    /// both objects will be cast to Foo for comparison) 
    /// </para> 
    /// </summary> 
    /// <typeparam name="T">Any class with public properties.</typeparam> 
    /// <param name="object1">Object to compare to object2.</param> 
    /// <param name="object2">Object to compare to object1.</param> 
    /// <param name="ignoredProperties">A list of <see cref="PropertyInfo"/> objects 
    /// to ignore when completing the comparison.</param> 
    /// <returns>A boolean value indicating whether or not the properties of each object match.</returns> 
    public static bool HasSamePropertyValues<T> (T object1 , T object2 , List<PropertyInfo> ignoredProperties) 
     where T : class 
    { 

     // If either object is null, we can't compare anything 
     if (object1 == null || object2 == null) 
     { 
      return false; 
     } 

     Type object1Type = object1.GetType(); 
     Type object2Type = object2.GetType(); 

     // In cases where object1 and object2 are of different Types (both being derived from Type T) 
     // we will cast both objects down to the base Type T to ensure the property comparison is only 
     // completed on COMMON properties. 
     // (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo -- 
     // both objects will be cast to Foo for comparison) 
     if (object1Type != object2Type) 
     { 
      object1Type = typeof (T); 
      object2Type = typeof (T); 
     } 

     // Remove any properties to be ignored 
     List<PropertyInfo> comparisonProps = 
      RemoveProperties(object1Type.GetProperties() , ignoredProperties); 

     foreach (PropertyInfo object1Prop in comparisonProps) 
     { 
      Type propertyType = null; 
      object object1PropValue = null; 
      object object2PropValue = null; 

      // Rule out an attempt to check against a property which requires 
      // an index, such as one accessed via this[] 
      if (object1Prop.GetIndexParameters().GetLength(0) == 0) 
      { 
       // Get the value of each property 
       object1PropValue = object1Prop.GetValue(object1 , null); 
       object2PropValue = object2Type.GetProperty(object1Prop.Name).GetValue(object2 , null); 

       // As we are comparing 2 objects of the same type we know 
       // that they both have the same properties, so grab the 
       // first non-null value 
       if (object1PropValue != null) 
        propertyType = object1PropValue.GetType().GetInterface("IComparable"); 

       if (propertyType == null) 
        if (object2PropValue != null) 
         propertyType = object2PropValue.GetType().GetInterface("IComparable"); 
      } 

      // If both objects have null values or were indexed properties, don't continue 
      if (propertyType != null) 
      { 
       // If one property value is null and the other is not null, 
       // they aren't equal; this is done here as a native CompareTo 
       // won't work with a null value as the target 
       if (object1PropValue == null || object2PropValue == null) 
       { 
        return false; 
       } 

       // Use the native CompareTo method 
       MethodInfo nativeCompare = propertyType.GetMethod("CompareTo"); 

       // Sanity Check: 
       // If we don't have a native CompareTo OR both values are null, we can't compare; 
       // hence, we can't confirm the values differ... just go to the next property 
       if (nativeCompare != null) 
       { 
        // Return the native CompareTo result 
        bool equal = (0 == (int) (nativeCompare.Invoke(object1PropValue , new object[] {object2PropValue}))); 

        if (!equal) 
        { 
         return false; 
        } 
       } 
      } 
     } 
     return true; 
    } 

    /// <summary> 
    /// Removes any <see cref="PropertyInfo"/> object in the supplied List of 
    /// properties from the supplied Array of properties. 
    /// </summary> 
    /// <param name="allProperties">Array containing master list of 
    /// <see cref="PropertyInfo"/> objects.</param> 
    /// <param name="propertiesToRemove">List of <see cref="PropertyInfo"/> objects to 
    /// remove from the supplied array of properties.</param> 
    /// <returns>A List of <see cref="PropertyInfo"/> objects.</returns> 
    private static List<PropertyInfo> RemoveProperties (
     IEnumerable<PropertyInfo> allProperties , IEnumerable<PropertyInfo> propertiesToRemove) 
    { 
     List<PropertyInfo> innerPropertyList = new List<PropertyInfo>(); 

     // Add all properties to a list for easy manipulation 
     foreach (PropertyInfo prop in allProperties) 
     { 
      innerPropertyList.Add(prop); 
     } 

     // Sanity check 
     if (propertiesToRemove != null) 
     { 
      // Iterate through the properties to ignore and remove them from the list of 
      // all properties, if they exist 
      foreach (PropertyInfo ignoredProp in propertiesToRemove) 
      { 
       if (innerPropertyList.Contains(ignoredProp)) 
       { 
        innerPropertyList.Remove(ignoredProp); 
       } 
      } 
     } 

     return innerPropertyList; 
    } 
} 
5

El problema real: ¿Cómo obtener la diferencia de dos conjuntos?

La manera más rápida que he encontrado es convertir los juegos a diccionarios primero, luego diff 'em. He aquí un planteamiento genérico:

static IEnumerable<T> DictionaryDiff<K, T>(Dictionary<K, T> d1, Dictionary<K, T> d2) 
{ 
    return from x in d1 where !d2.ContainsKey(x.Key) select x.Value; 
} 

entonces usted puede hacer algo como esto:

static public IEnumerable<PropertyInfo> PropertyDiff(Type t1, Type t2) 
{ 
    var d1 = t1.GetProperties().ToDictionary(x => x.Name); 
    var d2 = t2.GetProperties().ToDictionary(x => x.Name); 
    return DictionaryDiff(d1, d2); 
} 
4

La comparación de dos objetos del mismo tipo utilizando LINQ y reflexión. ¡NB! Esto es básicamente una reescritura de la solución de Jon Skeet, pero con una sintaxis más compacta y moderna. También debería generar un poco más de efecto IL.

Es algo parecido a esto:

public bool ReflectiveEquals(LocalHdTicket serverTicket, LocalHdTicket localTicket) 
    { 
    if (serverTicket == null && localTicket == null) return true; 
    if (serverTicket == null || localTicket == null) return false; 

    var firstType = serverTicket.GetType(); 
    // Handle type mismatch anyway you please: 
    if(localTicket.GetType() != firstType) throw new Exception("Trying to compare two different object types!"); 

    return !(from propertyInfo in firstType.GetProperties() 
       where propertyInfo.CanRead 
       let serverValue = propertyInfo.GetValue(serverTicket, null) 
       let localValue = propertyInfo.GetValue(localTicket, null) 
       where !Equals(serverValue, localValue) 
       select serverValue).Any(); 
    } 
+2

¿sería útil la recursión? reemplace la línea 'where! Equals (serverValue, localValue)' con 'firstType.IsValueType? ! Equals (serverValue, localValue):! ReflectiveEquals (serverValue, localValue) ' – drzaus

+3

Puede ser más moderno, pero no más compacto. Acabas de deshacerte de un montón de espacios en blanco e hiciste más difícil leer. –

+0

EliezerSteinbock, ese no es el caso. Si bien se deshizo de los espacios en blanco y dificultó la lectura, eso no es SÓLO lo que hizo. La instrucción LINQ allí compila de forma diferente a la declaración foreach en la respuesta de @ jon-skeet. Prefiero la respuesta de Jon porque es un sitio de ayuda, y su formateo es más claro, pero para una respuesta más avanzada, esta también es buena. –