differences between two objects in C#

2020-02-28 10:18发布

问题:

I was wondering how I would find the difference between two objects of the same class. So if I had a Person class with the only difference being Age it will return the field/fields that are different.

Thanks

回答1:

This isn't something that C# (or .NET really) supports directly, however you could implement something by hand for specific types, or write code that uses reflection to diff arbitrary objects.

If you choose the later, you will have to decide how deep you want to go into the object graph to identify whether two instances are identical or not and how you will compare certain primitive types for equality (doubles, for instance).

Writing a reflection-based differencing algorithm is more difficult that it seems at first - personally, I would implement this functionality directly for the types (or within a helper class) where you need it.



回答2:

Here's some simple code I use for just such a thing while debugging:

    //This structure represents the comparison of one member of an object to the corresponding member of another object.
    public struct MemberComparison
    {
        public readonly MemberInfo Member; //Which member this Comparison compares
        public readonly object Value1, Value2;//The values of each object's respective member
        public MemberComparison(MemberInfo 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 List<MemberComparison> ReflectiveCompare<T>(T x, T y)
    {
        List<MemberComparison> list = new List<MemberComparison>();//The list to be returned

        foreach (MemberInfo m in typeof(T).GetMembers(BindingFlags.NonPublic | BindingFlags.Instance))
            //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.MemberType == MemberTypes.Field)
            {
                FieldInfo field = (FieldInfo)m;
                var xValue = field.GetValue(x);
                var yValue = field.GetValue(y);
                if (!object.Equals(xValue, yValue))//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(field, yValue, xValue));
            }
            else if (m.MemberType == MemberTypes.Property)
            {
                var prop = (PropertyInfo)m;
                if (prop.CanRead && prop.GetGetMethod().GetParameters().Length == 0)
                {
                    var xValue = prop.GetValue(x, null);
                    var yValue = prop.GetValue(y, null);
                    if (!object.Equals(xValue, yValue))
                        list.Add(new MemberComparison(prop, xValue, yValue));
                }
                else//Ignore properties that aren't readable or are indexers
                    continue;
            }
        return list;
    }

To use it, your code might look something like this:

public static void Main()
{
    MyObject object1 = new MyObject();
    MyObject object2 = new MyObject();
    // ...Code that changes object1 and/or object2...

    //Here's your answer: a list of what's different between the 2 objects, and each of their different values.
    //No type parameters are needed here- typeof(MyObject) is implied by the coincident types of both parameters.
    List<MemberComparison> changes = ReflectiveCompare(object1, object2);
}


回答3:

It really depends on how deep you want to go in comparing the entities, but the idea with reflection is the best one here. The code would be something like that:

public class Pair
{
    public object Value1
    {
        get;
        set;
    }

    public object Value2
    {
        get;
        set;
    }
}

//somewhere in another class

public Dictionary<string, Pair> Compare<T>(T object1, T object2)
{
    var props = typeof(T).GetProperties().Where(pi => pi.CanRead); //this will return only public readable properties. Modify if you need something different
    Dictionary<string, Pair> result = new Dictionary<string, Pair>();
    foreach (var prop in props)
    {
        var val1 = prop.GetValue(object1, null); //indexing properties are ignored here
        var val2 = prop.GetValue(object2, null);
        if (val1 != val2) //maybe some more sophisticated compare algorithm here, using IComparable, nested objects analysis etc.
        {
            result[prop.Name] = new Pair { Value1 = val1, Value2 = val2 };
        }
    }
    return result;
}

If you need to deeply process nested objects, then, as it was said before, you will need some hash table that will remember already processed objects and prevent them from being processed again. Hope this helps!



回答4:

I used Michael Hoffmann's answer, however I found it lacking support if one of the properties is null, and if one throws an error (usually found when comparing "Type" objects), or if one is a collection.

while there is still work to be done, I am posting here the base modified code:

 public struct MemberComparison
    {
        public readonly System.Reflection.MemberInfo Member; //Which member this Comparison compares
        public readonly object Value1, Value2;//The values of each object's respective member
        public readonly Exception Value1Exception, Value2Exception;
        public MemberComparison(System.Reflection.MemberInfo member, object value1, object value2, Exception value1Exception = null, Exception value2Exception = null)
        {
            Member = member;
            Value1 = value1;
            Value2 = value2;
            Value1Exception = value1Exception;
            Value2Exception = value2Exception;
        }

        public override string ToString()
        {
            if (Value1Exception != null && Value2Exception != null)
            {
                if (Value1Exception.GetType().Equals(Value2Exception.GetType()))
                {
                     return Member.Name + ": Exception in both, same exception type of type "+Value1Exception.GetType().Name+", message in first exception: " +Value1Exception.Message+", message in second exception: "+Value2Exception.Message+", differences in type value: " + string.Join("\n", ReflectiveCompare(Value1Exception, Value2Exception).ToArray());
                }
                else if (!Value1Exception.GetType().Equals(Value2Exception.GetType()))
                {
                    return Member.Name + ": Exception in both, different exception type: " + Value1Exception.GetType().Name + " : " + Value2Exception.GetType().Name+", message in first exception: " +Value1Exception.Message+", message in second exception: "+Value2Exception.Message;
                }                    
            }
            else if (Value1Exception != null && Value2Exception == null)
            {                    
                   return Member.Name + ": "+ Value2.ToString()+" Exception in first of type " + Value1Exception.GetType().Name+", message is: "+Value1Exception.Message;
            } 
            else if (Value1Exception == null && Value2Exception != null)
            {                    
                   return Member.Name + ": "+ Value1.ToString()+" Exception in second of type " + Value2Exception.GetType().Name+", message is: "+Value2Exception.Message;
            }                
            return Member.Name + ": " + Value1.ToString() + (Value1.Equals(Value2) ? " == " : " != ") + Value2.ToString();
        }
    }


    public static bool isCollection(object obj)
    {
        return obj.GetType().GetInterfaces()
    .Any(iface => (iface.GetType() == typeof(ICollection) || iface.GetType() == typeof(IEnumerable) || iface.GetType() == typeof(IList)) || (iface.IsGenericTypeDefinition && (iface.GetGenericTypeDefinition() == typeof(ICollection<>) || iface.GetGenericTypeDefinition() == typeof(IEnumerable<>) || iface.GetGenericTypeDefinition() == typeof(IList<>))));
    }

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

    var memb = typeof(T).GetMembers();
    foreach (System.Reflection.MemberInfo m in memb)
        //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.MemberType == System.Reflection.MemberTypes.Field)
        {
            System.Reflection.FieldInfo field = (System.Reflection.FieldInfo)m;
            Exception excep1 = null;
            Exception excep2 = null;
            object xValue = null;
            object yValue = null;
            try
            {
                xValue = field.GetValue(x);
            }
            catch (Exception e)
            {
                excep1 = e;
            }
            try
            {
                yValue = field.GetValue(y);
            }
            catch (Exception e)
            {
                excep2 = e;
            }
            if ((excep1 != null && excep2 == null) || (excep1 == null && excep2 != null)) { list.Add(new MemberComparison(field, yValue, xValue, excep1, excep2)); }
            else if (excep1 != null && excep2 != null && !excep1.GetType().Equals(excep2.GetType())) { list.Add(new MemberComparison(field, yValue, xValue, excep1, excep2)); }
            else if (excep1 != null && excep2 != null && excep1.GetType().Equals(excep2.GetType()) && ReflectiveCompare(excep1, excep2).Count > 0) { list.Add(new MemberComparison(field, yValue, xValue, excep1, excep2)); }
            else if ((xValue == null && yValue == null)) { continue; }
            else if (xValue == null || yValue == null) list.Add(new MemberComparison(field, yValue, xValue));
            else if (!xValue.Equals(yValue) && ((!isCollection(xValue) && !isCollection(yValue)) || (isCollection(xValue) && !isCollection(yValue)) || (!isCollection(xValue) && isCollection(yValue)) || (isCollection(xValue) && isCollection(yValue) && ReflectiveCompare(xValue, yValue).Count > 0)))//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(field, yValue, xValue));
        }
        else if (m.MemberType == System.Reflection.MemberTypes.Property)
        {
            var prop = (System.Reflection.PropertyInfo)m;
            if (prop.CanRead && !(prop.GetGetMethod() == null || prop.GetGetMethod().GetParameters() == null) && prop.GetGetMethod().GetParameters().Length == 0)
            {                    
                Exception excep1 = null;
                Exception excep2 = null;
                object xValue = null;
                object yValue = null;
                try
                {
                    xValue = prop.GetValue(x, null);
                }
                catch (Exception e)
                {
                    excep1 = e;
                }
                try
                {
                    yValue = prop.GetValue(y, null);
                }
                catch (Exception e)
                {
                    excep2 = e;
                }
                if ((excep1 != null && excep2 == null) || (excep1 == null && excep2 != null)) { list.Add(new MemberComparison(prop, yValue, xValue, excep1, excep2)); }
                else if (excep1 != null && excep2 != null && !excep1.GetType().Equals(excep2.GetType())) { list.Add(new MemberComparison(prop, yValue, xValue, excep1, excep2)); }
                else if (excep1 != null && excep2 != null && excep1.GetType().Equals(excep2.GetType()) && ReflectiveCompare(excep1, excep2).Count > 0) { list.Add(new MemberComparison(prop, yValue, xValue, excep1, excep2)); }
                else if ((xValue == null && yValue == null)) { continue; }
                else if (xValue == null || yValue == null) list.Add(new MemberComparison(prop, yValue, xValue));
                else if (!xValue.Equals(yValue) && ((!isCollection(xValue) && !isCollection(yValue)) || (isCollection(xValue) && !isCollection(yValue)) || (!isCollection(xValue) && isCollection(yValue)) || (isCollection(xValue) && isCollection(yValue) && ReflectiveCompare(xValue,yValue).Count > 0)))// || (isCollection(xValue) && isCollection(yValue)  && ((IEnumerable<T>)xValue).OrderBy(i => i).SequenceEqual(xValue.OrderBy(i => i))) )))
                    list.Add(new MemberComparison(prop, xValue, yValue));
            }
            else//Ignore properties that aren't readable or are indexers
                continue;
        }
    return list;
        }


回答5:

What about something like this

This gets you a list of the property names that are different between the two objects. I don't think this is all the way to the solution you are looking for but I think it is a decent start

Foo foo1 = new Foo { Prop1 = "One", Prop2 = "Two"};
Foo foo2 = new Foo { Prop1 = "One", Prop2 = "Three" };

Type fooType = typeof (Foo);

PropertyInfo[] properties = fooType.GetProperties();

var diffs = from property in properties
  let first = foo1
  let second = foo2
  where property.GetValue(first, null) != property.GetValue(second, null)
  select property;

In my example this would return "Prop2" as that is the property whose values differ betwen the objects.

EDIT: Of course this assumes any complex types in your object implement equality comparisons that do what you expect. If not you would need to dive down the object graph and do nested compares as others have suggested



回答6:

You will need to recursively go through all private and public properties and fields on the entire object graph. Use a HashSet to keep track of objects you have already checked so you don't return duplicate results or get into a stack overflow.

If the property type is IComparable, you can cast the values of that property to IComparable and use IComparable.CompareTo. If not, you'll have to recursively call the your differential method on the sub-objects.