C#中两个对象之间的区别

7
我在想如何找到同一类别中两个对象之间的差异。因此,如果我有一个人类,唯一的区别是年龄,它将返回不同的字段/字段。
谢谢

你想问一下,是否有一个通用的对象比较器,可以比较两个相同类型的对象并给出不同属性的名称吗? - Unmesh Kondolikar
6个回答

9
这并非C#(或.NET)直接支持的功能,但您可以手动实现某些特定类型的内容,或编写使用反射来比较任意对象的代码。
如果选择后者,则必须决定要深入到对象图中多深以确定两个实例是否相同,并且如何比较某些原始类型的相等性(例如双精度浮点数)。
编写基于反射的差异算法比起一开始看起来更加困难-个人而言,我会在需要时直接为类型(或在帮助程序类中)实现此功能。

1
为什么有必要决定递归的深度?难道不能编写一个递归算法来实现这个吗? - fearofawhackplanet
1
@fearofawhackplanet:这是一个需求问题。尝试将对象图与无限深度进行比较并不总是有意义的。这也更加复杂,因为它需要您对任意复杂的图形执行循环检测 - 这是可能的,但可能会很棘手。 - LBushkin
我不知道最后那一部分是什么意思,但我会相信你的话 :) - fearofawhackplanet
@fearofawhackplanet:循环检测指的是您可以拥有其引用形成循环链的对象:A -> B -> C -> A。在这种情况下,如果不检测是否已经访问了这些对象的算法,将会进入无限循环(或者如果递归,则可能溢出堆栈)。通常,您需要使用HashSet或Dictionary来跟踪已经访问过的对象,以避免多次访问它们,并确保diff输出没有重复信息。 - LBushkin

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
我喜欢这个解决方案,但比较应该是:!object.Equals(xValue,yValue),以处理yValue为空的情况。 - DShook

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
我使用了Michael Hoffmann所提供的答案,但我发现该答案不支持属性为空的情况,也无法处理错误(通常出现在比较“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;
        }

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”,因为这是两个对象之间值不同的属性。
编辑:当然,这假设您对象中的任何复杂类型实现了相等比较并执行您期望的操作。如果没有,您需要深入对象图并执行嵌套比较,就像其他人建议的那样。

1

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接