Comparing Nested object properties using C#
I have a method which compares two objects and returns a list of all the property names which are different.
public static IList<string> GetDifferingProperties(object source, object target)
{
var sourceType = source.GetType();
var sourceProperties = sourceType.GetProperties();
var targetType = target.GetType();
var targetProperties = targetType.GetProperties();
var properties = (from s in sourceProperties
from t in targetProperties
where s.Name == t.Name &&
s.PropertyType == t.PropertyType &&
s.GetValue(source,null) != t.GetValue(target,null)
select s.Name).ToList();
return properties;
}
For example if I have two classes as follows:
public class Address
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
}
public class Employee
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public Address EmployeeAddress { get; set; }
}
I am trying to compare the following two employee instances:
var emp1Address = new Address();
emp1Address.AddressLine1 = "Microsoft Corporation";
emp1Address.AddressLine2 = "One Microsoft Way";
emp1Address.City = "Redmond";
emp1Address.State = "WA";
emp1Address.Zip = "98052-6399";
var emp1 = new Employee();
emp1.FirstName = "Bill";
emp1.LastName = "Gates";
emp1.EmployeeAddress = emp1Address;
var emp2Address = new Address();
emp2Address.AddressLine1 = "Gates Foundation";
emp2Address.AddressLine2 = "One Microsoft Way";
emp2Address.City = "Redmond";
emp2Address.State = "WA";
emp2Address.Zip = "98052-6399";
var emp2 = new Employee();
emp2.FirstName = "Melinda";
emp2.LastName = "Gates";
emp2.EmployeeAddress = emp2Address;
So when I pass these two employee objects to my GetDifferingProperties method currently it returns FirstName and EmployeeAddress, but it does not tell me which exact property (which in this case is Address1) in the EmployeeAddress has changed. How can I tweak this method to get something like Employee开发者_运维知识库Address.Address1?
It's because you are using !=
which, for objects, tests the identity of an object rather than its value. The key is to use recursion to generate the list of properties of properties. This will go as deep as you want...
public static IList<string> GetDifferingProperties(object source, object target)
{
var sourceType = source.GetType();
var sourceProperties = sourceType.GetProperties();
var targetType = target.GetType();
var targetProperties = targetType.GetProperties();
var result = new List<string>();
foreach (var property in
(from s in sourceProperties
from t in targetProperties
where s.Name == t.Name &&
s.PropertyType == t.PropertyType &&
!Equals(s.GetValue(source, null), t.GetValue(target, null))
select new { Source = s, Target = t }))
{
// it's up to you to decide how primitive is primitive enough
if (IsPrimitive(property.Source.PropertyType))
{
result.Add(property.Source.Name);
}
else
{
foreach (var subProperty in GetDifferingProperties(
property.Source.GetValue(source, null),
property.Target.GetValue(target, null)))
{
result.Add(property.Source.Name + "." + subProperty);
}
}
}
return result;
}
private static bool IsPrimitive(Type type)
{
return type == typeof(string) || type == typeof(int);
}
I can recommend using http://comparenetobjects.codeplex.com/ This has a possibility to compare nested objects, enums, ILists, etc. The project is free and easy to use(Just 1 .cs file). Moreover, it is possible to get the values that are different, add properties to ignore, etc.
In principle, you'll need to use the technique you implemented in GetDifferingProperties
on the two objects that you want to compare after you get their values (using GetValue
in the query). Probably the most straightforward implementation is to make the method recursive:
public static IEnumerable<string> GetDifferingProperties
(object source, object target) {
// Terminate recursion - equal objects don't have any differing properties
if (source == target) return new List<string>();
// Compare properties of two objects that are not equal
var sourceProperties = source.GetType().GetProperties();
var targetProperties = target.GetType().GetProperties();
return
from s in sourceProperties
from t in targetProperties
where s.Name == t.Name && s.PropertyType == t.PropertyType
let sVal = s.GetValue(source, null)
let tVal = t.GetValue(target, null)
// Instead of comparing the objects directly using '==', we run
// the method recursively. If the two objects are equal, it returns
// empty list immediately, otherwise it generates multiple properties
from name in GetDifferingProperties(sVal, tVal)
select name;
}
If you want to use this in practice, you'll probably want to keep track of how to get to the property (this code gives you just a list of property names without information about the object that contains them). You can change the last line from select name
to select s.Name + "." + name
which will give you a more complete name (e.g. Address.Name
if the property that differs is the Name
property of the Address
member).
One point: Your method is not accounting for actual differences in the the EmployeeAddress properties. Test it and see.
emp2Address.AddressLine1 = emp1Address.AddressLine1;// "Gates Foundation";
emp2Address.AddressLine2 = emp1Address.AddressLine2;// "One Microsoft Way";
emp2Address.City = emp1Address.City;// "Redmond";
emp2Address.State = emp1Address.State;// "WA";
emp2Address.Zip = emp1Address.Zip;// "98052-6399";
The program will still return EmployeeAddress as a non-matching property. However, if you simply set emp2.EmployeeAddress = emp1Address, you don't get the "non-match."
Something something about references...
At any rate, if you want to find what's different about that object, you're going to have to search for what's different about that object.
精彩评论