2009-06-05 42 views
114

Tôi có hai đối tượng cùng loại và tôi muốn lặp qua các thuộc tính công khai trên mỗi người trong số họ và cảnh báo người dùng về các thuộc tính nào không khớp.Vòng lặp thông qua một thuộc tính đối tượng Trong C#

Có thể thực hiện việc này mà không biết những thuộc tính nào chứa đối tượng không?

+0

Tôi nghĩ rằng đây sẽ giúp bạn ... [Duyệt qua các thuộc tính và các giá trị] (http://stackoverflow.com/questions/9724247/iterate-through-properties -and-values-of-an-object-return-via-a-linq-query-on) –

+0

Tôi nghĩ điều này sẽ giúp bạn. [Lặp lại thông qua các thuộc tính và giá trị của một đối tượng] (http://stackoverflow.com/questions/9724247/iterate-through-properties-and-values-of-an-object-returned-via-a-linq-query-on). –

Trả lời

161

Có, với phản xạ - giả định với từng loại tài sản thực hiện Equals một cách thích hợp. Một thay thế sẽ là sử dụng ReflectiveEquals đệ quy cho tất cả trừ một số loại đã biết, nhưng điều đó sẽ trở nên phức tạp.

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

Thêm phương pháp hiện đại hơn vào bên dưới ... –

+0

Có thể sử dụng đệ quy với phương pháp này và so sánh tất cả các bộ sưu tập mà đối tượng có thể có không? ví dụ. Object1 -> List (of School) -> List (của Classes) -> List (của học viên) –

+0

@PeterPitLock: Vâng, bạn có thể muốn xử lý khác nhau cho các bộ sưu tập - chỉ so sánh các thuộc tính trong danh sách sẽ không hoạt động tốt. –

28

Chắc chắn bạn có thể phản chiếu. Đây là mã để lấy các thuộc tính của một loại đã cho.

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

Nếu bạn có thể cung cấp thêm thông tin về những gì bạn đang so sánh về các thuộc tính chúng tôi có thể cùng nhau thuật toán khác biệt cơ bản. Mã này dành cho intstance sẽ khác với tên

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

Đánh tôi với nó. Drats. –

+0

Tôi nghĩ rằng anh ấy có nghĩa là hai đối tượng cùng loại với các giá trị * * không khớp. – BFree

+0

@JaredPar: Diffing Không hoạt động. Đối tượng PropertyInfo chắc chắn không giống hệt trừ khi chính loại đó là ... –

3

Có. Sử dụng Reflection. Với Reflection, bạn có thể làm những việc như:

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

Và sau đó bạn có thể sử dụng các lớp PropertyInfo để so sánh tất cả mọi thứ.

6

Tôi biết điều này có lẽ là quá mức cần thiết, nhưng đây là lớp ObjectComparer của tôi, tôi sử dụng cho mục đích này rất:

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

Vấn đề thực sự: Cách nhận sự khác biệt của hai bộ?

Cách nhanh nhất mà tôi đã tìm thấy là chuyển đổi các tập hợp thành từ điển trước, sau đó tìm khác biệt. Dưới đây là một cách tiếp cận chung:

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

Sau đó, bạn có thể làm một cái gì đó như thế này:

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

So sánh hai đối tượng cùng loại sử dụng LINQ và Reflection. NB! Điều này về cơ bản là viết lại các giải pháp từ Jon Skeet, nhưng với một cú pháp nhỏ gọn và hiện đại hơn. Nó cũng sẽ tạo ra một chút hiệu quả hơn IL.

Nó đi một cái gì đó như thế này:

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

đệ quy có hữu ích không? thay thế dòng 'where! Equals (serverValue, localValue)' bằng 'firstType.IsValueType? Bằng (serverValue, localValue):! ReflectiveEquals (serverValue, localValue) ' – drzaus

+3

Có thể hiện đại hơn, nhưng không gọn gàng hơn. Bạn vừa loại bỏ toàn bộ khoảng trắng và làm cho khó đọc hơn. –

+0

EliezerSteinbock hầu như không xảy ra. Trong khi anh ta đã loại bỏ khoảng trống và anh ta đã làm cho nó khó đọc hơn, đó không phải là JUST những gì anh ấy đã làm. Câu lệnh LINQ có biên dịch khác với câu lệnh foreach trong câu trả lời từ @ jon-skeet. Tôi thích câu trả lời của Jon bởi vì đây là một trang trợ giúp, và định dạng của anh ấy rõ ràng hơn, nhưng đối với câu trả lời tiên tiến hơn, câu trả lời này cũng tốt. –

Các vấn đề liên quan