C#中两个对象之间的差异

时间:2010-12-22 17:15:02

标签: c# reflection comparison

我想知道如何找到同一类的两个对象之间的区别。因此,如果我有一个Person类,唯一的区别是Age,它将返回不同的字段/字段。

由于

6 个答案:

答案 0 :(得分:9)

这不是C#(或.NET真正)直接支持的东西,但是您可以手动为特定类型实现某些东西,或编写使用反射来区分任意对象的代码。

如果选择后者,则必须决定要进入对象图的深度,以确定两个实例是否相同,以及如何比较某些基本类型的相等性(例如,双精度)。

编写基于反射的差分算法比起初看起来更困难 - 就个人而言,我会直接为您需要的类型(或帮助类)实现此功能。

答案 1 :(得分:8)

以下是我在调试时使用的一些简单代码:

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

要使用它,您的代码可能如下所示:

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

答案 2 :(得分:4)

这实际上取决于你想要比较实体的深度,但是反思的想法在这里是最好的。代码就是这样的:

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

如果您需要深度处理嵌套对象,那么,如前所述,您将需要一些哈希表来记住已处理的对象并防止再次处理它们。希望这可以帮助!

答案 3 :(得分:3)

我使用了Michael Hoffmann的答案,但是如果其中一个属性为null,并且如果一个抛出错误(通常在比较“Type”对象时发现),或者如果一个是集合,我发现它缺乏支持。

虽然仍有工作要做,但我在这里发布了基本修改代码:

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

答案 4 :(得分:2)

这样的事情

这将为您提供两个对象之间不同的属性名称列表。我不认为这一直是您正在寻找的解决方案,但我认为这是一个不错的开始

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;

在我的示例中,这将返回“Prop2”,因为这是属性,其值在对象之间不同。

编辑:当然,这假设对象中的任何复杂类型都实现了符合预期的相等比较。如果不是,您需要潜入对象图并按照其他人的建议进行嵌套比较

答案 5 :(得分:1)

您需要以递归方式遍历整个对象图上的所有私有和公共属性和字段。使用HashSet跟踪已经检查过的对象,这样就不会返回重复的结果或进入堆栈溢出。

如果属性类型是IComparable,则可以将该属性的值强制转换为IComparable并使用IComparable.CompareTo。如果没有,你将不得不以递归的方式在子对象上调用差分方法。