tags:

views:

213

answers:

4

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 EmployeeAddress.Address1?

A: 

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.

Anthony Pegram
+1  A: 

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);
}
+1  A: 

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).

Tomas Petricek
A: 

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.

ini18