Dec 22 2011

Compare two objects’ hierarchy

Category: C# | Linq — Duke @ 17:47

I think that everyone sometime is in the situation to understand which are the difference between two objects.

Sometime it is easy but sometime is not. Imagine that you have to prepare for your user a log of all the difference and changes that has been applied on a complex document object model.

I’m in this situation: I have NHibenrate that maps a database. As usual the principal entity of the domain have dozens and dozens of sub entities.

This complex structure is subject to revision from the user. There is a workflow to generate and approve a newer version of the object graph, but as the graph is complex, the user want to have a report of what is changed. from the last approved version. ok: now I have an graph of objects that represent the current situation, and a graph of objects that represent the last approved version.

How to solve this issue? Next: once you have discovered where the objects differs, you maybe want to see which is the actual value and understand which was the old value and which is the new one. Finally, maybe you want to revert some of the difference…. how you can do it in an efficient and powerful way? the reply is EXPRESSIONS From linq on, .NET have gained one of the best thing I ever seen in a programming language, the ability to manipulate code as it is data.

This possibility is SIMPLY AMAZING. I’ve already used this in SyncWcf and now I’ve another clue that makes me love expression more and more. so what I’ve done? give a look at the code below:

namespace PhotoAtomic.Reflection
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;

    public class DifferenceFinder
    {
        public ParameterExpression Parameter
        {
            get;
            private set;
        }

        public DifferenceFinder()
        {
            Type typeOfT = typeof(T);
            Parameter = Expression.Parameter(typeOfT, typeOfT.Name);
        }

        public object Evaluate(Expression expression, object onObject)
        {
            return Expression.Lambda(expression, Parameter).Compile().DynamicInvoke(onObject);
        }


        public IEnumerable FindDifferences(T a, T b)
        {
            Type typeOfT = typeof(T);

            ParameterExpression rootElement = Parameter;
            object rootObjectA = a;
            object rootObjectB = b;

            Dictionary, object> visitedObjects = new Dictionary, object>();


            return FindDifferences(typeOfT,rootElement, rootObjectA, rootObjectB, rootElement, a, b, visitedObjects);
        }

        private IEnumerable FindDifferences(Type typeOfT, ParameterExpression rootElement, object rootObjectA, object rootObjectB, Expression expression, object a, object b, Dictionary, object> visitedObjects)
        {

            if (IsValueEqual(a, b)) yield break;

            if (visitedObjects.ContainsKey(Tuple.Create(a, b))) yield break;
            visitedObjects.Add(Tuple.Create(a, b), null);

            if (IsKnownType(typeOfT))
            {
                yield return expression;
                yield break;
            }

            if ((a == null) ^ (b == null))
            {
                yield return expression;
                yield break;
            }
          

            IEnumerable itemsToCompare = FindChildItemsExpressions(typeOfT, expression);
            if (typeof(IEnumerable).IsAssignableFrom(typeOfT))
            {
                IEnumerable subItemsToCompare = FindCollectionItemsExpressions(typeOfT, expression, (IEnumerable)a, (IEnumerable)b);
                itemsToCompare = itemsToCompare.Union(subItemsToCompare);
            }

            foreach (var exp in itemsToCompare)
            {
                var accessor = Expression.Lambda(exp, rootElement);
                var compiled = accessor.Compile();
                object objA = compiled.DynamicInvoke(rootObjectA);
                object objB = compiled.DynamicInvoke(rootObjectB);
                var results = FindDifferences(exp.Type, rootElement, rootObjectA, rootObjectB, exp, objA, objB, visitedObjects);
                foreach (var innerDifference in results)
                {
                    yield return innerDifference;
                }
            }           
        }

    
        protected virtual bool IsValueEqual(object a, object b)
        {
            if ((a == null) && (b == null)) return true;
            if (a == null ^ b == null) return false;
            return a.Equals(b);            
        }
   

        protected virtual bool IsKnownType(Type typeOfT)
        {
            if (typeOfT.IsPrimitive) return true;
            if (typeOfT == typeof(string)) return true;
            return false;
        }

        protected virtual IEnumerable FindChildItemsExpressions(Type typeOfT, Expression start)
        {
            foreach (var property in typeOfT.GetProperties())
            {
                if (property.CanRead && property.GetIndexParameters().Length == 0)
                {
                    yield return Expression.MakeMemberAccess(start, property);
                }
            }
        }

        protected virtual IEnumerable FindCollectionItemsExpressions(Type typeOfT, Expression expression, IEnumerable a, IEnumerable b)
        {
            var typeOfItem = typeOfT.GetGenericArguments().FirstOrDefault();
            var enumerableExtensionsType = typeof(Enumerable);

            var max = Math.Max(a.OfType<object>().Count(), b.OfType<object>().Count());
            for (int i = 0; i < max; i++)
            {
                yield return Expression.Call(
                    enumerableExtensionsType,
                    "ElementAtOrDefault",
                    new[] { typeOfItem },
                    expression,
                    Expression.Constant(i));
            }

            yield break;
        }

    }
} 

not that long huh? but what this code do?

It takes two objects and starts comparing them property by property. if it finds that the value of these property are different it generates an expression (thanks to Riky for the great idea!) that points you in the place where the difference is. to tell the true it generates an expression for each difference Sorriso with these expressions then you can do what you want. you can inspect them, or use them to inspect the value that is different.

To do this the class provides an helper method “Evaluate” that takes one of these expressions, and the root object. it makes a lambda with the expression and pass the object as parameter, evaluating the expression is equivalent to retrieve the value.

But you can do much more I’ve marked some of the method virtual intentionally, this allows you to extend the class.

I’ve a derived class that makes an intelligent guess on the collection and compares the element to see what is different even if the items in the collections are ordered differently (while this base class uses the order of the collection)

I’ve also a tokenizer that splits the expression in tokens and a “humanizer” that transforms these tokens in a string that is “user readable” with the help of some custom attributes and the “ToString” method i ‘m able to transform the expression in a log that identifies what is changed. an idea of what is possible:

public IEnumerable GetHumanReadableDifferences(T a, T b)
        {

            var differenceFinder = new CollectionAwareDifferenceFinder(x => x.Id);
            var differences = differenceFinder.FindDifferences(a, b);
           
            foreach (var difference in differences)
            {
                
                var tokenizer = new Tokenizer();
                var tokens = tokenizer.Tokenize(difference);
                if (tokens
                    .Where(x=>x.Attributes!=null)
                    .SelectMany(x => x.Attributes)
                    .Count(attribute => attribute.GetType() == typeof(ComparisonIrrelevantAttribute)) > 0) continue;
                yield return Humanize(tokens, a, b, differenceFinder, difference);
            }            
        }

That’s easy, and incredibly powerful. I hope this code could help you.

One note: the code could be placed in a portable assembly (that’s why I’ve used a dictionary in place of a HashSet) I think the possibility with the expressions are near endless.

Learn how to master them, they are one of your most powerful ninja weapon!

Tags: , , , ,

1.
l&#39;hypertension pulmonaire l'hypertension pulmonaire says:

Very descriptive blog, I loved that a lot. Will there be a part 2?

Take a look at my web site ...  l'hypertension pulmonaire - impotenceanddiabeteshelp.com/.../

2.
http://wehelpbuyinshop.com/propeciasinrecetaar1/ http://wehelpbuyinshop.com/propeciasinrecetaar1/ says:

Hey there,  You have done a fantastic job. I'll definitely digg it and personally suggest to my friends. I am sure they will be benefited from this web site.

Feel free to visit my web blog detener la calvicie de patron masculino ( http://wehelpbuyinshop.com/propeciasinrecetaar1/ - http://wehelpbuyinshop.com/propeciasinrecetaar1/ )

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading