Compare properties automatically

I want to get the names of all the properties that have been changed to match the objects. I have these (simplified) classes:

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

Now I want to get objects whose values ​​are different:

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

In the end, I would like to have a Difference list as follows:

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

ChangedProperties must contain the name of the changed properties.

+8
comparison c # properties
source share
5 answers

I spent quite a bit of time trying to write a faster reflection based solution using typed delegates. But in the end, I gave up and switched to the Fast-Member library to achieve better performance than normal reflection.

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

Application:

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

Performance:

My very biased measurements showed that this approach is about 4-6 times faster than Json-Conversion, and about 9 times faster than regular reflections. But honestly, you could speed up the other solutions a bit.

Limitations:

Currently, my solution does not regress according to nested lists, for example, it does not compare individual Subject elements - it only detects that the lists of objects are different, but not what and where. However, it should not be too difficult to add this feature when you need it. The most difficult part is likely to be the decision on how to present these differences in the Difference class.

+5
source share

Let's start with 2 simple methods:

 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 just compares serialized versions of two objects using Json.Net, it does not allow to handle reference types and value types in different ways.

GetDifference checks the properties of the passed objects and compares them individually.

To get a list of differences:

 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(); 
+3
source share

Everyone is always trying to get a fantasy and write these too general ways to extract data. There are costs for this.

Why not become old school simplicity.

Have a member function of the GetDifferences function.

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

In inherited classes. Override and add their specific properties. AddRange is a basic function.

KISS - Keep It Simple. It will take you 10 minutes of the monkey to write it, and you know that it will work efficiently.

+1
source share

I do this using this:

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

Here you have the code that does what you want with 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; } 
0
source share

All Articles