Introduction

Extending functionality by attaching behaviors to object has advantages over extending functionality by modifying existing classes or creating new classes. However, its adoption is still limited. The reason, I think, is that either the tools available make it too complex or lack of features to do serious programming based on them. The situations are changing now. With new tools that are coming out with more features and are still simple to use, developers should seriously start to think extending functionality by attaching behaviors to object instead of extending functionality by modifying existing classes or creating new classes.

There are three tools that can be used to extend functionality of object in the .NET world. They are Castle DynamicProxy, Unity and Dynamic Decorator. This article discusses their performance and features in extending functionality of objects.

Scope

The CreateProxy method of the ObjectProxyFactory class in the Dynamic Decorator is mostly close to CreateInterfaceProxyWithTarget method of the ProxyGenerator class in the Castle DynamicProxy and ThroughProxy method of the Intercept in the Unity. Therefore, the discussion is limited to use them to add some preprocessing/postprocessing functionality to an existing object that implements an interface.

Test Code

The test code is listed as follows.

//EnterLogBehavior class for Unity
public class EnterLogBehavior : IInterceptionBehavior
{
    public IEnumerable<Type> GetRequiredInterfaces()
    {
        return Type.EmptyTypes;
    }

    public bool WillExecute
    {
        get { return true; }
    }

    public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
    {
        Console.Write("Calling: " + input.MethodBase.Name + "\n");
        var methodReturn = getNext().Invoke(input, getNext);
        return methodReturn;
    }
}

//ExitLogBehavior class for Unity
public class ExitLogBehavior : IInterceptionBehavior
{
    public IEnumerable<Type> GetRequiredInterfaces()
    {
        return Type.EmptyTypes;
    }

    public bool WillExecute
    {
        get { return true; }
    }

    public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
    {
        var methodReturn = getNext().Invoke(input, getNext);
        Console.Write("Exiting: " + input.MethodBase.Name + "\n");
        return methodReturn;
    }
}

//LoggingInterceptor class for Castle DynamicProxy
public class LoggingInterceptor : Castle.DynamicProxy.IInterceptor
{
    private int m_count;
    public void Intercept(IInvocation invocation)
    {

        Console.Write("Calling: " + invocation.Method.Name + "\n");
        invocation.Proceed();
        Console.Write("Exiting: " + invocation.Method.Name + "\n");
    }

}
    
class Program
{
    static void Main(string[] args)
    {
        IEmployee emp = new Employee(1, "John", "Smith", new DateTime(1990, 4, 1), 1);
        System.Int32? id = null;
        System.String detail = "";
        IEmployee iemp;
        TimeSpan? ts = null;

        System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();

        //Performance test for Unity
        stopWatch.Start();
        iemp = Intercept.ThroughProxy<IEmployee>(emp, new InterfaceInterceptor(),
            new[] { (IInterceptionBehavior)new EnterLogBehavior(), (IInterceptionBehavior)new ExitLogBehavior() });

        id = iemp.EmployeeID;
        detail = iemp.DetailsByLevel(2);
        stopWatch.Stop();
        ts = stopWatch.Elapsed;
        Console.Write("Using Unity ThroughProxy: " + ts.Value.ToString() + "\n\n");

        //Performance test for Castle DynamicProxy
        stopWatch.Restart();
        ProxyGenerator generator = new ProxyGenerator();
        iemp = generator.CreateInterfaceProxyWithTarget<IEmployee>(emp, new LoggingInterceptor());

        id = iemp.EmployeeID;
        detail = iemp.DetailsByLevel(2);
        stopWatch.Stop();
        ts = stopWatch.Elapsed;
        Console.Write("Using Castle CreateInterfaceProxyWithTarget: " + ts.Value.ToString() + "\n\n");

        //Performance test for Dynamic Decorator
        stopWatch.Restart();
        iemp = ObjectProxyFactory.CreateProxy<IEmployee>(
            emp,
            new String[] { "DetailsByLevel", "get_EmployeeID" },
            new Decoration((x, y) =>
            {
                Console.Write("Calling: " + x.CallCtx.MethodName + "\n");
            }, null),
            new Decoration((x, y) =>
            {
                Console.Write("Exiting: " + x.CallCtx.MethodName + "\n");
            }, null));

        id = iemp.EmployeeID;
        detail = iemp.DetailsByLevel(2);
        stopWatch.Stop();
        ts = stopWatch.Elapsed;
        Console.Write("Using Dynamic Decorator: " + ts.Value.ToString() + "\n\n");


        //More features for Dynamic Decorator
        emp = ObjectProxyFactory.CreateProxy<IEmployee>(
            emp,
            new String[] { "DetailsByLevel" },
            new Decoration((x, y) =>
            {
                IMethodCallMessage method = x.CallCtx;
                string str = "Calling " + x.Target.GetType().ToString() + "." + method.MethodName +
                    "(";
                int i = 0;
                foreach (object o in method.Args)
                {
                    if (i > 0)
                        str = str + ", ";
                    str = str + o.ToString();
                }
                str = str + ")";

                Console.WriteLine(str);
                Console.Out.Flush();
            }, null),
            null);

        id = emp.DepartmentID;
        detail = emp.DetailsByLevel(2);
    }
}

In the above code, an object emp of class Employee is created. First, the ThroughProxy method of the Intercept class of the Unity is used to add entering/exiting logs to object emp. Second, the CreateInterfaceProxyWithTarget method of the ProxyGenerator of Castle DynamicProxy is used to add entering/exiting logs to object emp. Third, the CreateProxy method of the ObjectProxyFactory class of the Dynamic Decorator is used to add entering/exiting logs to object emp.

When execution of the above code, you will see the following output:

First, let's take a look at the performance of each tools. For doing the exactly same thing, the Unity took 111.3ms, the Castle DynamicProxy took 248.3ms while the Dynamic Decorator took 3.9ms. Though all of them declare that they are lightweight tools, you can see which one is truely lightweight in adding extra behaviors to object. The Dynamic Decorator is clearly a winner in terms of performance.

Second, let's see how the behaviors are defined for each of them. For Unity, each of behaviors is a class implementing interface IInterceptionBehavior, e.g., EnterLogBehavior and ExitLogBehavior. For Castle DynamicProxy, the behaviors are in one single class LoggingInterceptor implementing interface Castle.DynamicProxy.IInterceptor. For Dynamic Decorator, each of behaviors is a method (the lambda expressions for this example). Therefore, conceptually, the Dynamic Decorator is simpler.

Next, let's look into more features available from the Dynamic Decorator. For Unity and Castle DynamicProxy, the behaviors are attached to all methods of the object. There is no easy way to specify which methods of the object to have the behaviors. It is either all methods or no methods to get the behaviors. With Dynamic Decorator, however, individual methods can be specified to have the extra behaviors. For instance, in the last part of the code, only the method DetailsByLevel is specified when calling ObjectProxyFactory.CreateProxy of the Dynamic Decorator. As a result, only DetailsByLevel method gets the extra behaviors. The other methods of the object do not get the extra behaviors. Therefore, the Dynamic Decorator is more flexible and granular.

Last, behaviors defined in the Dynamic Decorator are able to access the target object and call context. It is extremely handy to get runtime information. For instance, the lambda expression of the last ObjectProxyFactory.CreateProxy in the above code accesses the target object to get the type, and accesses the call context to get the method name and argument value. On the other hand, I am not aware of any examples dealing with context or target information for Unity or Castle DynamicProxy.

There are other features of Dynamic Decorator that can be used to enhance behaviors. Please see the article Add Aspects to Object Using Dynamic Decorator for more details and examples.

Conclusion

The Dynamic Decorator is specifically designed to extend functionality of object. It is simple to use, flexible and can be used to write extremely powerful behaviors. It is a truely lightweight and feature-rich tool to extend object functionality and to add aspects to object. On the other hand, although the Unity and Castle DynamicProxy can be used to extend functionality of object, they have significant performance overhead and limited features to enhance the behaviors.

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"