2013-06-17 80 views
8

Tôi muốn lấy tên của tất cả các thuộc tính đã thay đổi cho các đối tượng trùng khớp. Tôi có những (đơn giản hóa) lớp:So sánh các thuộc tính tự động

public enum PersonType { Student, Professor, Employee } 

class Person { 
    public string Name { get; set; } 
    public PersonType Type { get; set; } 
} 

class Student : Person { 
    public string MatriculationNumber { get; set; } 
} 

class Subject { 
    public string Name { get; set; } 
    public int WeeklyHours { get; set; } 
} 

class Professor : Person { 
    public List<Subject> Subjects { get; set; } 
} 

Bây giờ tôi muốn để có được các đối tượng nơi các giá trị tài sản khác nhau:

List<Person> oldPersonList = ... 
List<Person> newPersonList = ... 
List<Difference> = GetDifferences(oldPersonList, newPersonList); 

public List<Difference> GetDifferences(List<Person> oldP, List<Person> newP) { 
    //how to check the properties without casting and checking 
    //for each type and individual property?? 
    //can this be done with Reflection even in Lists?? 
} 

Cuối cùng tôi muốn có một danh sách Difference giống như này:

class Difference { 
    public List<string> ChangedProperties { get; set; } 
    public Person NewPerson { get; set; } 
    public Person OldPerson { get; set; } 
} 

ChangedProperties phải chứa tên của thuộc tính đã thay đổi.

+0

Làm điều này cho danh sách là một nỗi đau * thực * (giả sử bạn cần xử lý thêm/xóa/sắp xếp lại/etc); tuy nhiên, trên cơ sở từng đối tượng, vui lòng xem: http://stackoverflow.com/questions/3060382/comparing-2-objects-and-retrieve-a-list-of-fields-with-different-values ​​- cái nào chính xác số này –

+0

@MarcGravell: Tôi đã thử nó và nó trả về các thuộc tính được liệt kê dưới dạng delta. Dù sao cũng cảm ơn bạn. –

+0

Bạn có quan tâm đến các thuộc tính không có trong cả hai đối tượng, I.e. nên matriculationNumber được coi là một sự thay đổi khi bạn so sánh một người với một sinh viên? –

Trả lời

0

tôi đang làm nó bằng cách sử dụng này:

//This structure represents the comparison of one member of an object to the corresponding member of another object. 
    public struct MemberComparison 
    { 
     public static PropertyInfo NullProperty = null; //used for ROOT properties - i dont know their name only that they are changed 

     public readonly MemberInfo Member; //Which member this Comparison compares 
     public readonly object Value1, Value2;//The values of each object's respective member 
     public MemberComparison(PropertyInfo member, object value1, object value2) 
     { 
      Member = member; 
      Value1 = value1; 
      Value2 = value2; 
     } 

     public override string ToString() 
     { 
      return Member.name+ ": " + Value1.ToString() + (Value1.Equals(Value2) ? " == " : " != ") + Value2.ToString(); 
     } 
    } 

    //This method can be used to get a list of MemberComparison values that represent the fields and/or properties that differ between the two objects. 
    public static List<MemberComparison> ReflectiveCompare<T>(T x, T y) 
    { 
     List<MemberComparison> list = new List<MemberComparison>();//The list to be returned 

     if (x.GetType().IsArray) 
     { 
      Array xArray = x as Array; 
      Array yArray = y as Array; 
      if (xArray.Length != yArray.Length) 
       list.Add(new MemberComparison(MemberComparison.NullProperty, "array", "array")); 
      else 
      { 
       for (int i = 0; i < xArray.Length; i++) 
       { 
        var compare = ReflectiveCompare(xArray.GetValue(i), yArray.GetValue(i)); 
        if (compare.Count > 0) 
         list.AddRange(compare); 
       } 
      } 
     } 
     else 
     { 
      foreach (PropertyInfo m in x.GetType().GetProperties()) 
       //Only look at fields and properties. 
       //This could be changed to include methods, but you'd have to get values to pass to the methods you want to compare 
       if (!m.PropertyType.IsArray && (m.PropertyType == typeof(String) || m.PropertyType == typeof(double) || m.PropertyType == typeof(int) || m.PropertyType == typeof(uint) || m.PropertyType == typeof(float))) 
       { 
        var xValue = m.GetValue(x, null); 
        var yValue = m.GetValue(y, null); 
        if (!object.Equals(yValue, xValue))//Add a new comparison to the list if the value of the member defined on 'x' isn't equal to the value of the member defined on 'y'. 
         list.Add(new MemberComparison(m, yValue, xValue)); 
       } 
       else if (m.PropertyType.IsArray) 
       { 
        Array xArray = m.GetValue(x, null) as Array; 
        Array yArray = m.GetValue(y, null) as Array; 
        if (xArray.Length != yArray.Length) 
         list.Add(new MemberComparison(m, "array", "array")); 
        else 
        { 
         for (int i = 0; i < xArray.Length; i++) 
         { 
          var compare = ReflectiveCompare(xArray.GetValue(i), yArray.GetValue(i)); 
          if (compare.Count > 0) 
           list.AddRange(compare); 
         } 
        } 
       } 
       else if (m.PropertyType.IsClass) 
       { 
        var xValue = m.GetValue(x, null); 
        var yValue = m.GetValue(y, null); 
        if ((xValue == null || yValue == null) && !(yValue == null && xValue == null)) 
         list.Add(new MemberComparison(m, xValue, yValue)); 
        else if (!(xValue == null || yValue == null)) 
        { 
         var compare = ReflectiveCompare(m.GetValue(x, null), m.GetValue(y, null)); 
         if (compare.Count > 0) 
          list.AddRange(compare); 
        } 


       } 
     } 
     return list; 
    } 
+0

Tôi nhận được thông báo * Số tham số không khớp * ngoại lệ khi tôi sử dụng tính năng này. –

3

Chúng tôi bắt đầu với 2 phương pháp đơn giản:

public bool AreEqual(object leftValue, object rightValue) 
{ 
    var left = JsonConvert.SerializeObject(leftValue); 
    var right = JsonConvert.SerializeObject(rightValue); 

    return left == right; 
} 

public Difference<T> GetDifference<T>(T newItem, T oldItem) 
{ 
    var properties = typeof(T).GetProperties(); 

    var propertyValues = properties 
     .Select(p => new { 
      p.Name, 
      LeftValue = p.GetValue(newItem), 
      RightValue = p.GetValue(oldItem) 
     }); 

    var differences = propertyValues 
     .Where(p => !AreEqual(p.LeftValue, p.RightValue)) 
     .Select(p => p.Name) 
     .ToList(); 

    return new Difference<T> 
    { 
     ChangedProperties = differences, 
     NewItem = newItem, 
     OldItem = oldItem 
    }; 
} 

AreEqual chỉ so sánh các phiên bản serialized của hai đối tượng sử dụng Json.Net, điều này giúp nó xử lý các loại tham chiếu và loại giá trị khác nhau.

GetDifference kiểm tra các thuộc tính trên đối tượng được truyền trong đối tượng và so sánh chúng riêng lẻ.

Để có được một danh sách các khác biệt:

var oldPersonList = new List<Person> { 
    new Person { Name = "Bill" }, 
    new Person { Name = "Bob" } 
}; 

var newPersonList = new List<Person> { 
    new Person { Name = "Bill" }, 
    new Person { Name = "Bobby" } 
}; 

var diffList = oldPersonList.Zip(newPersonList, GetDifference) 
    .Where(d => d.ChangedProperties.Any()) 
    .ToList(); 
0

Ở đây bạn có một mã mà làm những gì bạn muốn với Reflection.

public List<Difference> GetDifferences(List<Person> oldP, List<Person> newP) 
    { 
     List<Difference> allDiffs = new List<Difference>(); 
     foreach (Person oldPerson in oldP) 
     { 
      foreach (Person newPerson in newP) 
      { 
       Difference curDiff = GetDifferencesTwoPersons(oldPerson, newPerson); 
       allDiffs.Add(curDiff); 
      } 
     } 

     return allDiffs; 
    } 

    private Difference GetDifferencesTwoPersons(Person OldPerson, Person NewPerson) 
    { 
     MemberInfo[] members = typeof(Person).GetMembers(); 

     Difference returnDiff = new Difference(); 
     returnDiff.NewPerson = NewPerson; 
     returnDiff.OldPerson = OldPerson; 
     returnDiff.ChangedProperties = new List<string>(); 
     foreach (MemberInfo member in members) 
     { 
      if (member.MemberType == MemberTypes.Property) 
      { 
       if (typeof(Person).GetProperty(member.Name).GetValue(NewPerson, null).ToString() != typeof(Person).GetProperty(member.Name).GetValue(OldPerson, null).ToString()) 
       { 
        returnDiff.ChangedProperties.Add(member.Name); 
       } 
      } 
     } 

     return returnDiff; 
    } 
5

Tôi đã dành khá nhiều thời gian để viết giải pháp dựa trên phản chiếu nhanh hơn bằng cách sử dụng các đại biểu được nhập. Nhưng cuối cùng tôi đã từ bỏ và chuyển sang Marc Gravell'sFast-Member library để đạt được hiệu suất cao hơn so với phản ánh bình thường.

Code:

internal class PropertyComparer 
{  
    public static IEnumerable<Difference<T>> GetDifferences<T>(PropertyComparer pc, 
                   IEnumerable<T> oldPersons, 
                   IEnumerable<T> newPersons) 
     where T : Person 
    { 
     Dictionary<string, T> newPersonMap = newPersons.ToDictionary(p => p.Name, p => p); 
     foreach (T op in oldPersons) 
     { 
      // match items from the two lists by the 'Name' property 
      if (newPersonMap.ContainsKey(op.Name)) 
      { 
       T np = newPersonMap[op.Name]; 
       Difference<T> diff = pc.SearchDifferences(op, np); 
       if (diff != null) 
       { 
        yield return diff; 
       } 
      } 
     } 
    } 

    private Difference<T> SearchDifferences<T>(T obj1, T obj2) 
    { 
     CacheObject(obj1); 
     CacheObject(obj2); 
     return SimpleSearch(obj1, obj2); 
    } 

    private Difference<T> SimpleSearch<T>(T obj1, T obj2) 
    { 
     Difference<T> diff = new Difference<T> 
           { 
            ChangedProperties = new List<string>(), 
            OldPerson = obj1, 
            NewPerson = obj2 
           }; 
     ObjectAccessor obj1Getter = ObjectAccessor.Create(obj1); 
     ObjectAccessor obj2Getter = ObjectAccessor.Create(obj2); 
     var propertyList = _propertyCache[obj1.GetType()]; 
     // find the common properties if types differ 
     if (obj1.GetType() != obj2.GetType()) 
     { 
      propertyList = propertyList.Intersect(_propertyCache[obj2.GetType()]).ToList(); 
     } 
     foreach (string propName in propertyList) 
     { 
      // fetch the property value via the ObjectAccessor 
      if (!obj1Getter[propName].Equals(obj2Getter[propName])) 
      { 
       diff.ChangedProperties.Add(propName); 
      } 
     } 
     return diff.ChangedProperties.Count > 0 ? diff : null; 
    } 

    // cache for the expensive reflections calls 
    private Dictionary<Type, List<string>> _propertyCache = new Dictionary<Type, List<string>>(); 
    private void CacheObject<T>(T obj) 
    { 
     if (!_propertyCache.ContainsKey(obj.GetType())) 
     { 
      _propertyCache[obj.GetType()] = new List<string>(); 
      _propertyCache[obj.GetType()].AddRange(obj.GetType().GetProperties().Select(pi => pi.Name)); 
     } 
    } 
} 

Cách sử dụng:

PropertyComparer pc = new PropertyComparer(); 
var diffs = PropertyComparer.GetDifferences(pc, oldPersonList, newPersonList).ToList(); 

Hiệu suất:

đo rất thiên vị của tôi cho thấy phương pháp này là nhanh hơn khoảng 4-6 lần Json-Conversion và khoảng 9 lần fas ter hơn phản xạ thông thường. Nhưng công bằng, bạn có thể tăng tốc các giải pháp khác một chút.

Hạn chế:

Tại thời điểm giải pháp của tôi không recurse trên danh sách lồng nhau, ví dụ nó không so sánh cá nhân Subject mục - nó chỉ phát hiện rằng các danh sách các đối tượng khác nhau, nhưng không phải là điều hay Ở đâu. Tuy nhiên, không quá khó để thêm tính năng này khi bạn cần. Phần khó nhất có thể là quyết định cách trình bày những khác biệt này trong lớp học Difference.

1

Mọi người luôn cố gắng để có được ưa thích và viết những cách quá chung chung của việc trích xuất dữ liệu. Có một chi phí cho điều đó.

Tại sao không phải là trường học cũ đơn giản.

Có chức năng thành viên GetDifferences Person.

virtual List<String> GetDifferences(Person otherPerson){ 
    var diffs = new List<string>(); 
    if(this.X != otherPerson.X) diffs.add("X"); 
    .... 
} 

Trong các lớp kế thừa. Ghi đè và thêm các thuộc tính cụ thể của chúng. AddRange chức năng cơ sở.

KISS - Hãy đơn giản. Nó sẽ đưa bạn 10 phút của công việc khỉ để viết nó, và bạn biết nó sẽ có hiệu quả và làm việc.

+1

Thao tác này sẽ hoạt động. Nhưng sẽ không mất 10 phút. Các lớp học của tôi phức tạp hơn nhiều so với ví dụ mà tôi đưa ra. Nó sẽ mất ít nhất một ngày, với các dòng mã hundrets và các lỗi có thể có trong mã đó. –

+1

Hãy chắc chắn rằng bạn cân nhắc perf trên nó sau đó. Nếu số lượng so sánh thấp, thì đường phản chiếu là tốt. Nếu bạn đang chạy điều này trên hàng trăm nghìn + thì tôi sẽ cảnh giác hơn. – CodeMonkeyForHire