11

I need to create a generic method, which will take two objects (of same type), and return list of properties which have different values. As my requirement is bit different I don't think this as duplicate.

public class Person
{
   public string Name {get;set;}
   public string Age {get;set;}
}

Person p1 = new Person{FirstName = "David", Age = 33}
Person p2 = new Person{FirstName = "David", Age = 44}

var changedProperties = GetChangedProperties(p1,p2);

The code explains the requirement:

public List<string> GetChangedProperties(object A, object B)
{
    List<string> changedProperties = new List<string>();
   //Compare for changed values in properties 
   if(A.Age != B.Age)
   {
       //changedProperties.Add("Age");
   } 
   //Compare other properties
   ..
   ..
   return changedProperties;
}

Should consider following:

  1. Generic - Should be able to compare any type of objects (with same class)
  2. Performance
  3. Simple

Is there any libraries available out of the box there?

Can I achieve this using AutoMapper?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Rahul
  • 2,431
  • 3
  • 35
  • 77
  • I recommend to set a `dirty`-Flag whenever something has changed. It's much faster (and easier) than comparing the whole object. – Psi Mar 15 '17 at 12:27
  • 1
    You can enumerate [properties of the type](http://stackoverflow.com/q/737151/1997232), obtaining values from both instances, comparing and return names of those what are different. – Sinatr Mar 15 '17 at 12:28
  • 1
    @Psi No, i cant do that since these objects are changed by some API methods which i don't have control over it. – Rahul Mar 15 '17 at 12:31
  • @Sinatr I'm not sure about the performance when comparing the large objects. So I'm looking for some alternatives. – Rahul Mar 15 '17 at 12:33
  • @Rahul If you want a method that will work with any classes out of your control then you have to use reflection and it will have a performance hit. The alternative is writing code specific to the classes or class you want to compare. – juharr Mar 15 '17 at 12:39
  • Object comparison is a difficult topic, especially with constraint #1. The answers below don't deal with lists or dictionaries. You'll agree that `var a = new [] { 5, 2, 3 }` and `var b = new [] { 5, 2, 3 }` are identical. One doesn't deal with nested classes. Databases (SQL server, for example) can truncate date information leading to incorrect comparisons. You can have issues with comparisons involving floating points. You have to factor in nullables (e.g. `int? a`), etc. These are just a few things to think about. – ProgrammingLlama Mar 15 '17 at 13:28
  • P.S. One method is to serialize both classes and compare the resultant strings. This typically averts the aforementioned issues with things like lists, dictionaries, class equality, etc. but you'll still potentially have issues with dates, floating point numbers, etc. – ProgrammingLlama Mar 15 '17 at 13:43
  • @Rahul try my approach – Marco Salerno Mar 15 '17 at 16:53

4 Answers4

12

I improved a little on Krishnas answer:

public List<string> GetChangedProperties<T>(object A, object B)
{
    if (A != null && B != null)
    {
        var type = typeof(T);
        var allProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
        var allSimpleProperties = allProperties.Where(pi => pi.PropertyType.IsSimpleType());
        var unequalProperties =
               from pi in allSimpleProperties
               let AValue = type.GetProperty(pi.Name).GetValue(A, null)
               let BValue = type.GetProperty(pi.Name).GetValue(B, null)
               where AValue != BValue && (AValue == null || !AValue.Equals(BValue))
               select pi.Name;
        return unequalProperties.ToList();
    }
    else
    {
        throw new ArgumentNullException("You need to provide 2 non-null objects");
    }
}

because it wasn't working for me. This one does and the only other thing you need to make it work is the IsSimpleType()-Extension Method that I adapted from this answer here (I only converted it into an extension method).

public static bool IsSimpleType(this Type type)
{
    if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
    {
        // nullable type, check if the nested type is simple.
        return type.GetGenericArguments()[0].IsSimpleType();
    }
    return type.IsPrimitive
      || type.IsEnum
      || type.Equals(typeof(string))
      || type.Equals(typeof(decimal));
}
robkrueger
  • 166
  • 2
  • 7
2

Try this. should be generic for any class.

 public List<string> GetChangedProperties(object A, object B)
    {
       if (A!= null && B != null)
        {
            var type = typeof(T);
         var unequalProperties =
                from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                where pi.GetUnderlyingType().IsSimpleType() && pi.GetIndexParameters().Length == 0
                let AValue = type.GetProperty(pi.Name).GetValue(A, null)
                let BValue = type.GetProperty(pi.Name).GetValue(B, null)
                where AValue != BValue && (AValue == null || !AValue.Equals(BValue))
                select pi.Name;
     return unequalProperties.ToList();
         }
    }
Krishna
  • 529
  • 3
  • 13
0
using System;
using System.Collections.Generic;
using System.Reflection;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Person p1 = new Person("David", 33);
            Person p2 = new Person("David", 44);

            var changedProperties = GetChangedProperties(p1, p2);
        }

        public class Person
        {
            public Person(string name, int age)
            {
                this.name = name;
                this.age = age;
            }

            public int age { get; set; }
            public string name { get; set; }
        }

        public static List<string> GetChangedProperties(Object A, Object B)
        {
            if (A.GetType() != B.GetType())
            {
                throw new System.InvalidOperationException("Objects of different Type");
            }
            List<string> changedProperties = ElaborateChangedProperties(A.GetType().GetProperties(), B.GetType().GetProperties(), A, B);
            return changedProperties;
        }


        public static List<string> ElaborateChangedProperties(PropertyInfo[] pA, PropertyInfo[] pB, Object A, Object B)
        {
            List<string> changedProperties = new List<string>();
            foreach (PropertyInfo info in pA)
            {
                object propValueA = info.GetValue(A, null);
                object propValueB = info.GetValue(B, null);
                if (propValueA != propValueB)
                {
                    changedProperties.Add(info.Name);
                }
            }
            return changedProperties;
        }
    }
}
Cesare
  • 9,139
  • 16
  • 78
  • 130
Marco Salerno
  • 5,131
  • 2
  • 12
  • 32
-1

This was the simplest solution I could come up with. Generic, Performant and simple. However, a prerequisite is that if any properties are class objects, they must implement equals correctly.

public List<string> GetChangedProperties<T>(T a, T b) where T:class
{   
    if (a != null && b != null)
    {
        if (object.Equals(a, b))
        {
            return new List<string>();
        }
        var allProperties = a.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
        return allProperties.Where(p => !object.Equals( p.GetValue(a),p.GetValue(b))).Select(p => p.Name).ToList();
    }
    else
    {
        var aText = $"{(a == null ? ("\"" + nameof(a) + "\"" + " was null") : "")}";
        var bText = $"{(b == null ? ("\"" + nameof(b) + "\"" + " was null") : "")}";
        var bothNull = !string.IsNullOrEmpty(aText) && !string.IsNullOrEmpty(bText);
        throw new ArgumentNullException(aText + (bothNull ? ", ":"" )+ bText );
    }
}