2013-06-17 24 views
8

Chcę uzyskać nazwy wszystkich właściwości, które zostały zmienione dla pasujących obiektów. mam te (uproszczony) klasy:Porównywanie właściwości automatycznie

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

teraz chcę dostać obiekty gdzie wartości właściwości różnią:

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

W końcu chciałbym mieć listę Difference Like to:

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

Nazwa ChangedProperties powinna zawierać nazwę zmienionych właściwości.

+0

Robiąc to dla list jest * prawdziwy * ból (przy założeniu, że musisz obsługiwać dodawanie/usuwanie/ponowne zamówienie/etc); jednak w odniesieniu do poszczególnych obiektów, zobacz: http://stackoverflow.com/questions/3060382/porównanie-2-objects-and-retrieve-a-list-of-fields-with-different-values ​​- which does dokładnie to –

+0

@MarcGravell: Próbowałem go i zwraca atrybuty, które są listami jako delta. Dzięki i tak. –

+0

Dbasz o właściwości, które nie znajdują się w obu obiektach, tj. powinno być uznawane za stopęLiczba uznawana za zmianę, gdy porównujesz osobę z uczniem? –

Odpowiedz

0

Robię to za pomocą tego:

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

Otrzymuję wyjątek * niezgodność count * wyjątku, gdy używam tego. –

3

Zaczynamy z 2 prostych metod:

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 tylko porównuje odcinkach wersje dwóch obiektów wykorzystujących Json.NET ta utrzymuje ją od różnego traktowania typów odniesienia i typów wartości.

GetDifference sprawdza właściwości przekazanych obiektów i porównuje je indywidualnie.

Aby uzyskać listę różnic:

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

Tutaj masz kod, który robi to, co chcesz z 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

Spędziłem sporo czasu, próbując napisać szybsze rozwiązanie oparte na odbiciu za pomocą wpisanych delegatów. Ale ostatecznie zrezygnowałem i przełączyłem na Marc Gravell'sFast-Member library, aby osiągnąć wyższą wydajność niż przy normalnym odbiciu.

Kod:

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

Zastosowanie:

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

Wydajność:

Moi bardzo tendencyjne pomiary wykazały, że podejście to jest około 4-6 razy szybciej niż Konwersja Json i około 9 razy fas ter niż zwykłe odbicia. Ale jeśli chodzi o uczciwość, możesz prawdopodobnie przyspieszyć inne rozwiązania.

Ograniczenia:

W tej chwili moje rozwiązanie nie recurse nad zagnieżdżonych list, na przykład, że nie ma porównania poszczególnych Subject przedmioty - to tylko wykryje, że listy przedmioty są różne, ale nie to, co lub gdzie. Jednak dodanie tej funkcji w razie potrzeby nie powinno być zbyt trudne. Najtrudniejszą częścią byłoby prawdopodobnie zdecydować, jak przedstawić te różnice w klasie Difference.

1

Każdy zawsze stara się wymyślić i napisać te zbyt ogólne sposoby wyodrębniania danych. Koszt to.

Dlaczego nie być prostą szkołą?

Masz funkcję członka GetDifferences Osoba.

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

W klasach odziedziczonych. Zastąp i dodaj ich określone właściwości. AddRange funkcja podstawowa.

KISS - Zachowaj proste. Napisanie go zajęłoby ci 10 minut pracy z małpami i wiesz, że będzie to skuteczne i skuteczne.

+1

To zadziała. Ale to nie zajmie 10 minut. Moje zajęcia są dużo bardziej złożone niż przykład, który dałem. Zajmie to co najmniej jeden dzień, z setkami linii kodu i możliwymi błędami w tym kodzie. –

+1

Upewnij się, że zważyłeś wtedy na nim perf. W przypadku małej liczby porównań, droga odbicia jest w porządku. Jeśli używasz tego ponad setki tysięcy, to byłbym bardziej ostrożny. – CodeMonkeyForHire