两个对象通过所有属性的属性递归迭代比较? [英] Comparing two objects by iterating recursively through all the properties' properties?

查看:444
本文介绍了两个对象通过所有属性的属性递归迭代比较?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我写了一个测试方法一类的两个实例之间的比较(类型兼容性的假设给出)。宝德我检查了所有的公共属性,确保返回的差异列表。



的问题是,一些属性是包含自己的属性(子属性的对象,如果你愿意)。那些没有被比较,因为到目前为止,我可以通过工艺流程步进看到的。



如何设计一个呼叫的推移在深入比较所有子属性?额外奖金,如果该方法比较简单。 :)

 公共静态类扩展
{
公共静态的IEnumerable<串GT; DiffersOn<通用>(
这个通用的自我,通用的另一个),其中通用:类
{
如果(个体经营== NULL ||另一个== NULL)
产量返回NULL;
$ B $型B型= ty​​peof运算(通用);
IEnumerable的<&的PropertyInfo GT;性能= type.GetProperties(
BindingFlags.Public | BindingFlags.Instance);

的foreach(在属性中的PropertyInfo财产)
{
VAR自拍= type.GetProperty(property.Name).GetValue(个体经营);
VAR othie = type.GetProperty(property.Name).GetValue(另一人);
如果(自拍照= othie和放大器;!及(自拍== NULL || selfie.Equals(othie))!)
收益率的回报property.Name;
}
}
}


解决方案

正如我在评论说,最简单的方法是使用的BinaryFormatter 序列化对象和比较原始的字节[] 流。随着你会比较字段(而不是属性)这样的事情可能会有所不同(两个对象可作为比较平等的逻辑,即使他们的私人领域是不同的)。最大的优点是序列化将处理一个非常棘手的情况:当对象具有循环引用



大致是这样的:

 静态布尔CheckForEquality(对象A,对象b)
{
的BinaryFormatter格式=新的BinaryFormatter();使用(MemoryStream的streamA =新的MemoryStream())
使用

(MemoryStream的streamB =新的MemoryStream())
{
formatter.Serialize(streamA,A);
formatter.Serialize(streamB,B);

如果(streamA.Length = streamB.Length!)
返回false;

streamA.Seek(0,SeekOrigin.Begin);
streamB.Seek(0,SeekOrigin.Begin);

为(int值= 0;(值= streamA.ReadByte())> = 0;)
{
如果(值= streamB.ReadByte()!)
返回FALSE;
}

返回真;
}
}



正如本·福格特在评论该算法指出,比较流是相当缓慢的,对于一个快速缓冲比较(的MemoryStream 保存在字节[] 缓冲区中的数据)看这个帖子他建议说。



如果您需要更多的控制而实际处理自定义比较,那么你必须让事情变得更加复杂。接下来样本比较的第一个原始(和未经考验的!)版本。它不处理非常重要的事情:循环引用

 静态布尔CheckForEquality(对象A,对象B)
{
如果(Object.ReferenceEquals(A,b))
返回真;

//这是有点任意的,如果b有一个自定义比较
//可能为null,那么这将绕过。然而
//这是相当罕见的,非空的对象等于
//空(除非是零和b为空< T>
//没有价值)。记住,这...
如果(Object.ReferenceEquals(一,NULL)
返回FALSE;

//这里我们处理默认和自定义比较假设
//类型是良好形成,并以良好的生活习惯。Hashcode方法
//检查是一个微型的优化,它可以加速向上检查
//不平等(如果哈希值不同,那么我们完全可以
//假设对象不相等......在结构良好的对象)
如果和放大器。(Object.ReferenceEquals(b,空!);&安培; a.GetHashCode()= b! GetHashCode()方法)
返回FALSE;

如果(a.Equals(b))
返回真;

VAR comparableA = a作为IComparable的;
如果(comparableA!= NULL)
返回comparableA.CompareTo(二)== 0;

//不同实例,其中一个为空,它们是不同的,除非
//这是一个特殊的情况下,由a的对象(IComparable的)
如果(Object.ReferenceEquals(二,空))
返回假处理;

//如果b具有类型的对象一个
自定义比较//而不是相反。
如果(b.Equals(A))
返回真;

//我们假设,我们只能比较同一类型。这不是真的
//因为自定义比较运营商,但它也应该是
//在处理的Object.Equals()。
变种类型= a.GetType();
如果(类型= b.GetType()!)
返回false;对列表

//特殊情况,他们将不会匹配,但我们可能会考虑
//他们是否等于他们有相同的元素,并在相应的每个元素匹配
//其他对象。
//这个比较敏感的顺序所以A,B,C!= C,B,A。
//项目必须首先下令,如果这不是你想要的。
//还要注意的是一个更好的实现应该检查
//的ICollection作为特殊情况和IEnumerable应该使用。
//一个更好的实现也应该检查
// IStructuralComparable和IStructuralEquatable实现。
VAR = listA的一个作为System.Collections.ICollection;
如果(为listA!= NULL)
{
变种数组listB = B为System.Collections.ICollection;

如果(listA.Count = listB.Count!)
返回FALSE;

VAR aEnumerator = listA.GetEnumerator();
VAR bEnumerator = listB.GetEnumerator();

,而(aEnumerator.MoveNext()及和放大器; bEnumerator.MoveNext())(!CheckForEquality(aEnumerator.Current,bEnumerator.Current))
{
如果
返回FALSE;
}

//我们不返回true这里,一个类可以实现IList,也有
//许多其他属性,我们比较
去}

//如果我们来到这里,我们必须通过
执行财产//属性比较递归调用这个函数。
//注意,这里我们检查公共接口的平等。
VAR性能= type.GetProperties()式(X =>!x.GetMethod = NULL);
的foreach(在性能VAR属性)
{
如果
返回假(CheckForEquality(property.GetValue(一),property.GetValue(B))!);
}

//如果我们来到这里,然后对象可以被认为是相等的
返回真;
}

如果你去掉注释你就会有很短的代码。为了处理循环引用,你必须避免一次又一次同样的元组进行比较,要做到这一点,你必须像在这个例子中分割功能(非常非常幼稚的做法,我知道):

 静态布尔CheckForEquality(对象A,对象b)
{
返回CheckForEquality(新名单,LT元组LT;对象,对象>>()A, b);
}

通过这样的核心实现(我只重写重要组成部分):

 静态布尔CheckForEquality(列表<元组LT;对象,对象>> visitedObjects,
对象,对象b)
{
//如果我们比较了在此之前的元组和我们仍然比较
//那么我们就可以把它们看成相等(或不相关)。
如果(visitedObjects.Contains(Tuple.Create(A,B)))
返回真;

visitedObjects.Add(Tuple.Create(A,B));

//去,并传递给visitedObjects递归调用
}

下一步是点点复杂(获得不同的属性列表),因为它可能不会是这样简单的(例如,如果两个属性列表和他们有不同数量的项目)。我就小品一个可能的解决方案(为清晰起见循环引用删除代码)。需要注意的是,当平等则打破了后续检查也可能会产生意外的异常,因此应该实施比这更好



新的原型将是:



 静态无效CheckForEquality(对象A,对象b,列表与LT;串>差异)
{
CheckForEquality(,A,b ,差异);
}

和实现方法还需要跟踪当前路径:

 静态无效CheckForEquality(字符串路径,
对象,对象b,
名单<串>差异)
{
如果(a.Equals(b))
的回报;

VAR comparableA = a作为IComparable的;
如果(comparableA =空&放大器;!&放大器; comparableA.CompareTo(二)!= 0)
differences.Add(路径);

如果(Object.ReferenceEquals(B,NULL))
{
differences.Add(路径);
的回报; //这是必需的:没有别的比较
}

如果(b.Equals(A))
返回真;

变种类型= a.GetType();
如果(类型= b.GetType()!)
{
differences.Add(路径);
的回报; //这是必需的:我们不能去比较不同类型
}

VAR = listA的一个作为System.Collections.ICollection;
如果(为listA!= NULL)
{
变种数组listB = B为System.Collections.ICollection;

如果(listA.Count == listB.Count)
{
VAR aEnumerator = listA.GetEnumerator();
VAR bEnumerator = listB.GetEnumerator();

INT I = 0;
,而(aEnumerator.MoveNext()及和放大器; bEnumerator.MoveNext())
{
CheckForEquality(
的String.Format({0} [{1}] ,路径,我++),
aEnumerator.Current,bEnumerator.Current,差异);
}
}
,否则
{
differences.Add(路径);
}
}

VAR性能= type.GetProperties()式(X =>!x.GetMethod = NULL);
的foreach(在性能VAR属性)
{
CheckForEquality(
的String.Format({0}。{1},路径,property.Name),
property.GetValue(a)中,property.GetValue(b)中的差异);
}
}


I've written a test method for comparison between two instances of a class (the assumption of type compatibility is given). Proudly I checked all the public properties making sure to return a list of discrepancies.

The problem is that some of the properties are objects containing their own properties (sub-properties, if you will). Those are not being compared, as far I can see by stepping through the process flow.

How can I design a call that goes in on-depth and compares all sub-properties? Extra bonus if the approach is relatively simple. :)

public static class Extensions
{
  public static IEnumerable<string> DiffersOn<Generic>(
    this Generic self, Generic another) where Generic : class
  {
    if (self == null || another == null)
      yield return null;

    Type type = typeof(Generic);
    IEnumerable<PropertyInfo> properties = type.GetProperties(
      BindingFlags.Public | BindingFlags.Instance);

    foreach (PropertyInfo property in properties)
    {
      var selfie = type.GetProperty(property.Name).GetValue(self);
      var othie = type.GetProperty(property.Name).GetValue(another);
      if (selfie != othie && (selfie == null || !selfie.Equals(othie)))
        yield return property.Name;
    }
  }
}

解决方案

As I said in comment easiest way is to use BinaryFormatter to serialize both objects and compare raw byte[] streams. With that you'll compare fields (and not properties) so things may be different (two objects may be compared as logically equal even if their private fields are different). Biggest advantage is that serialization will handle a very tricky case: when objects have circular references.

Roughly something like this:

static bool CheckForEquality(object a, object b)
{
    BinaryFormatter formatter = new BinaryFormatter();

    using (MemoryStream streamA = new MemoryStream())
    using (MemoryStream streamB = new MemoryStream())
    {
        formatter.Serialize(streamA, a);
        formatter.Serialize(streamB, b);

        if (streamA.Length != streamB.Length)
            return false;

        streamA.Seek(0, SeekOrigin.Begin);
        streamB.Seek(0, SeekOrigin.Begin);

        for (int value = 0; (value = streamA.ReadByte()) >= 0; )
        {
            if (value != streamB.ReadByte())
                return false;
        }

        return true;
    }
}

As pointed out by Ben Voigt in a comment this algorithm to compare streams is pretty slow, for a fast buffer comparison (MemoryStream keeps data in a byte[] buffer) see this post he suggested.

If you need more "control" and actually handle custom comparison then you have to make things more complicated. Next sample is first raw (and untested!) version of this comparison. It doesn't handle a very important thing: circular references.

static bool CheckForEquality(object a, object b)
{
    if (Object.ReferenceEquals(a, b))
        return true;

    // This is little bit arbitrary, if b has a custom comparison
    // that may equal to null then this will bypass that. However
    // it's pretty uncommon for a non-null object to be equal
    // to null (unless a is null and b is Nullable<T>
    // without value). Mind this...
    if (Object.ReferenceEquals(a, null)
        return false; 

    // Here we handle default and custom comparison assuming
    // types are "well-formed" and with good habits. Hashcode
    // checking is a micro optimization, it may speed-up checking
    // for inequality (if hashes are different then we may safely
    // assume objects aren't equal...in "well-formed" objects).
    if (!Object.ReferenceEquals(b, null) && a.GetHashCode() != b.GetHashCode())
        return false;

    if (a.Equals(b))
        return true;

    var comparableA = a as IComparable;
    if (comparableA != null)
        return comparableA.CompareTo(b) == 0;

    // Different instances and one of them is null, they're different unless
    // it's a special case handled by "a" object (with IComparable).
    if (Object.ReferenceEquals(b, null))
        return false;

    // In case "b" has a custom comparison for objects of type "a"
    // but not vice-versa.
    if (b.Equals(a))
        return true; 

    // We assume we can compare only the same type. It's not true
    // because of custom comparison operators but it should also be
    // handled in Object.Equals().
    var type = a.GetType();
    if (type != b.GetType())
        return false;

    // Special case for lists, they won't match but we may consider
    // them equal if they have same elements and each element match
    // corresponding one in the other object.
    // This comparison is order sensitive so A,B,C != C,B,A.
    // Items must be first ordered if this isn't what you want.
    // Also note that a better implementation should check for
    // ICollection as a special case and IEnumerable should be used.
    // An even better implementation should also check for
    // IStructuralComparable and IStructuralEquatable implementations.
    var listA = a as System.Collections.ICollection;
    if (listA != null)
    {
        var listB = b as System.Collections.ICollection;

        if (listA.Count != listB.Count)
            return false;

        var aEnumerator = listA.GetEnumerator();
        var bEnumerator = listB.GetEnumerator();

        while (aEnumerator.MoveNext() && bEnumerator.MoveNext())
        {
            if (!CheckForEquality(aEnumerator.Current, bEnumerator.Current))
                return false;
        }

        // We don't return true here, a class may implement IList and also have
        // many other properties, go on with our comparison
    }

    // If we arrived here we have to perform a property by
    // property comparison recursively calling this function.
    // Note that here we check for "public interface" equality.
    var properties = type.GetProperties().Where(x => x.GetMethod != null);
    foreach (var property in properties)
    {
        if (!CheckForEquality(property.GetValue(a), property.GetValue(b)))
            return false;
    }

    // If we arrived here then objects can be considered equal
    return true;
}

If you strip out comments you'll have pretty short code. To handle circular references you have to avoid to compare again and again same tuple, to do that you have to split function like in this example (very very naive implementation, I know):

static bool CheckForEquality(object a, object b)
{
    return CheckForEquality(new List<Tuple<object, object>>(), a, b);
}

With core implementation like this (I rewrite only important part):

static bool CheckForEquality(List<Tuple<object, object>> visitedObjects, 
                             object a, object b)
{
    // If we compared this tuple before and we're still comparing
    // then we can consider them as equal (or irrelevant).
    if (visitedObjects.Contains(Tuple.Create(a, b)))
        return true;

    visitedObjects.Add(Tuple.Create(a, b));

    // Go on and pass visitedObjects to recursive calls
}

Next step is little bit more complicate (get the list of different properties) because it may not be such simple (for example if two properties are lists and they have different number of items). I'll just sketch a possible solution (removing code for circular references for clarity). Note that when equality breaks then subsequent checks may also produce unexpected exceptions so it should be implemented much better than this.

New prototype will be:

static void CheckForEquality(object a, object b, List<string> differences)
{
     CheckForEquality("", a, b, differences);
}

And implementation method will also need to keep track of "current path":

static void CheckForEquality(string path,
                             object a, object b, 
                             List<string> differences)
{
    if (a.Equals(b))
        return;

    var comparableA = a as IComparable;
    if (comparableA != null && comparableA.CompareTo(b) != 0)
        differences.Add(path);

    if (Object.ReferenceEquals(b, null))
    {
        differences.Add(path);
        return; // This is mandatory: nothing else to compare
    }

    if (b.Equals(a))
        return true;

    var type = a.GetType();
    if (type != b.GetType())
    {
        differences.Add(path);
        return; // This is mandatory: we can't go on comparing different types
    }

    var listA = a as System.Collections.ICollection;
    if (listA != null)
    {
        var listB = b as System.Collections.ICollection;

        if (listA.Count == listB.Count)
        {
            var aEnumerator = listA.GetEnumerator();
            var bEnumerator = listB.GetEnumerator();

            int i = 0;
            while (aEnumerator.MoveNext() && bEnumerator.MoveNext())
            {
                CheckForEquality(
                    String.Format("{0}[{1}]", path, i++),
                    aEnumerator.Current, bEnumerator.Current, differences);
            }
        }
        else
        {
            differences.Add(path);
        }
    }

    var properties = type.GetProperties().Where(x => x.GetMethod != null);
    foreach (var property in properties)
    {
        CheckForEquality(
            String.Format("{0}.{1}", path, property.Name),
            property.GetValue(a), property.GetValue(b), differences);
    }
}

这篇关于两个对象通过所有属性的属性递归迭代比较?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆