2009-06-05 20 views
114

Ich habe zwei Objekte des gleichen Typs, und ich möchte die öffentlichen Eigenschaften auf jedem von ihnen durchlaufen und den Benutzer darüber informieren, welche Eigenschaften nicht übereinstimmen.Schleife durch Objekte Eigenschaften in C#

Ist es möglich, dies zu tun, ohne zu wissen, welche Eigenschaften das Objekt enthält?

+0

Ich denke, das wird Ihnen helfen, ... [Iterate durch Eigenschaften und Werte] (http://stackoverflow.com/questions/9724247/iterate-through-properties -und-Werte-eines-Objekts-zurückgegeben-via-a-linq-Abfrage-on) –

+0

Ich denke, das wird Ihnen helfen. [Iterate durch Eigenschaften und Werte eines Objekts] (http://stackoverflow.com/questions/9724247/iterate-through-properties-and-values-of-anobject-returned-via-a-linq-query-on). –

Antwort

161

Ja, mit Reflexion - jede Eigenschaft Typ implementiert Equals entsprechend angenommen wird. Eine Alternative wäre, rekursiv ReflectiveEquals für alle außer einigen bekannten Typen zu verwenden, aber das wird schwierig.

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

Hinzugefügt einen moderneren Ansatz dazu unten ... –

+0

Wäre es möglich, Rekursion mit dieser Methode zu verwenden, und vergleichen Sie alle Sammlungen, die das Objekt haben kann? z.B. Objekt1 -> Liste (der Schule) -> Liste (der Klassen) -> Liste (der Schüler) –

+0

@PeterPitLock: Nun, Sie würden wahrscheinlich unterschiedliche Behandlung für Sammlungen wollen - nur Eigenschaften auf Listen vergleichen würde nicht gut funktionieren. –

28

Sicher kannst du mit Reflexion. Hier ist der Code zum Abrufen der Eigenschaften eines bestimmten Typs.

Wenn Sie mehr Informationen darüber geben können, was Sie über die Eigenschaften vergleichen, können wir einen grundlegenden Diffingalgorithmus zusammenstellen. Dieser Code für die Anwesenheit wird diff auf Namen

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

Beat mich dazu. Drums. –

+0

Ich denke, er meinte zwei Objekte des gleichen Typs, wo die * Werte * nicht übereinstimmen. – BFree

+0

@JaredPar: Diffing funktioniert nicht. PropertyInfo-Objekte sind sicherlich nicht identisch, es sei denn der Typ selbst ist ... –

3

Ja. Verwenden Sie Reflection. Mit Reflexion können Sie tun Dinge wie:

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

Und dann kann man die resultierenden Property Klassen verwenden, um alle möglichen Dinge zu vergleichen.

6

Ich weiß, dass dies wahrscheinlich übertrieben ist, aber hier ist meine ObjectComparer Klasse I für diesen Zweck verwenden:

/// <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

Das eigentliche Problem: Wie bekomme ich den Unterschied von zwei Sätzen?

Der schnellste Weg, den ich gefunden habe, besteht darin, zuerst die Sätze in Wörterbücher zu konvertieren und dann zu unterscheiden. Hier ist ein allgemeiner Ansatz:

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

Dann können Sie etwas tun:

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

Vergleich zweier Objekte des gleichen Typs mit LINQ und Reflexion. ACHTUNG! Dies ist im Grunde eine Neuschreibung der Lösung von Jon Skeet, aber mit einer kompakteren und moderneren Syntax. Es sollte auch etwas mehr effictive IL erzeugen.

Es geht ungefähr so:

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

wäre Rekursion nützlich? Ersetzen Sie die Zeile 'where! Equals (serverValue, localValue)' 'durch' firstType.IsValueType? ! Equals (serverValue, localValue):! ReflectiveEquals (serverValue, localValue) ' – drzaus

+3

Vielleicht moderner, aber nicht kompakter. Sie haben gerade eine ganze Reihe von Leerzeichen entfernt und es schwerer lesbar gemacht. –

+0

EliezerSteinbock, das ist kaum der Fall. Während er Whitespace los wurde und er es schwerer machte zu lesen, ist das nicht nur das, was er getan hat. Die LINQ-Anweisung wird dort anders kompiliert als die foreach-Anweisung in der Antwort von @ jon-skeet. Ich bevorzuge Jons Antwort, weil dies eine Hilfeseite ist, und seine Formatierung ist klarer, aber für eine erweiterte Antwort ist diese auch gut. –

Verwandte Themen