Click here to Skip to main content
15,886,362 members
Articles / Programming Languages / C#

Dynamic Decorator and Castle DynamicProxy Comparison

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
1 Jul 2011CPOL3 min read 35.2K   199   8   13
Compare performance and features between Dynamic Decorator and Castle DynamicProxy

Introduction

Recently, I got a chance to play with Castle DynamicProxy a bit and did some comparisons between it and Dynamic Decorator. I found some interesting points and would like to share in this blog.

They are similar in that both use proxy concept. Both can be used to extend object functionality and add aspects to objects without modifying existing classes. The difference is that Dynamic Decorator overrides .NET's RealProxy and reuses its transparent proxy technology while Castle DynamicProxy uses a customized proxy technology. Your can find more information about them in the following links:

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. The test is limited to use them to add some preprocessing functionality to an existing object.

Test Code

In this test, we try to add some logging functionality to an existing object. The following is the code for this test.

C#
public class LoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Console.Write("Calling: " + invocation.Method.Name + "\n");
        invocation.Proceed();
    }
}

class Program
{
    static void Main(string[] args)
    {
        string path = Path.GetDirectoryName
		(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
        FileStream fileStream = null;
        if (!File.Exists(path + "\\hrlog.txt"))
        {
            fileStream = new FileStream(path + "\\hrlog.txt", FileMode.Create);
        }
        else
            fileStream = new FileStream(path + "\\hrlog.txt", FileMode.Truncate);

        TextWriter tmp = Console.Out;
        StreamWriter sw1 = new StreamWriter(fileStream);
        Console.SetOut(sw1);

        IEmployee emp = new Employee(1, "John", "Smith", new DateTime(1990, 4, 1), 1);
        IEmployee iemp = null;
        System.Int32? id = null;
        System.String detail = "";
        DateTime? dtStart = null;
        TimeSpan? ts = null;

        //Performance test for Castle DynamicProxy
        dtStart = DateTime.Now;
        for (int i = 0; i < 1000; i++)
        {
            ProxyGenerator generator = new ProxyGenerator();
            iemp = generator.CreateInterfaceProxyWithTarget<IEmployee>
					(emp, new LoggingInterceptor());

            id = iemp.EmployeeID;
            detail = iemp.DetailsByLevel(2);
        }
        ts = DateTime.Now - dtStart.Value;
        Console.Write("Using Castle CreateInterfaceProxyWithTarget: " + 
					ts.Value.ToString() + "\n");

        //Performance test for Dynamic Decorator
        dtStart = DateTime.Now;
        for (int i = 0; i < 1000; i++)
        {
            iemp = (IEmployee)ObjectProxyFactory.CreateProxy(
                emp,
                new String[] { "DetailsByLevel" },
                new Decoration((x, y) =>
                {
                    Console.Write("Calling: " + x.CallCtx.MethodName + "\n");
                }, null),
                null);

            id = iemp.DepartmentID;
            detail = iemp.DetailsByLevel(2);
        }
        ts = DateTime.Now - dtStart.Value;
        Console.Write("Using Dynamic Decorator: " + ts.Value.ToString() + "\n");

        //More features for Dynamic Decorator
        emp = (IEmployee)ObjectProxyFactory.CreateProxy(
            emp,
            new String[] { "DetailsByLevel" },
            new Decoration((x, y) =>
            {
                IMethodCallMessage method = x.CallCtx;
                string str = "Entering " + 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);

        Console.SetOut(tmp);
        sw1.Close();
        fileStream.Close();
    }
}

Performance

The first part of the code compares Castle DynamicProxy's performance with Dynamic Decorator's. In the above code, an object emp of class Employee is created. The first loop simply calls CreateInterfaceProxyWithTarget method of the ProxyGenerator class of the Castle DynamicProxy to create a proxy, then, uses the proxy to call the methods of the object emp for 1000 times. The second loop simply calls CreateProxy method of the ObjectProxyFactory class of the Dynamic Decorator, then, uses the proxy to call the methods of the object emp for 1000 times. The execution time of each loop is presented in the following table.

Castle DynamicProxy - First LoopDynamic Decorator - Second Loop
00:01:13.497648000:00:00.0781225

The Dynamic Decorator is orders of magnitude faster than the Castle DynamicProxy for doing exactly the same thing. Actually, the execution time for Dynamic Decorator is negligible. The Dynamic Decorator is clearly a winner in terms of performance.

Features

The following paragraphs discuss the feature differences between Dynamic Decorator and Castle DynamicProxy.

With Castle DynamicProxy, the preprocessing functionality is added to all methods of the object. There is no easy way to specify a particular method to decorate. It is either all methods or no methods to get the additional functionality. With Dynamic Decorator, however, individual methods can be specified. For instance, in the above code, we specify only the method DetailsByLevel when calling ObjectProxyFactory.CreateProxy of the Dynamic Decorator.

With Dynamic Decorator, you can access the method call context from within additional functionality. For example, in the above code, we are able to get the value of the parameter of the method DetailsByLevel. I am not aware of a way to do it in the Castle DynamicProxy.

From the above code, you see the proxy of the Dynamic Decorator can be chained. The variable emp originally references to the target object. And later, it references to the proxy of the target. You can keep doing this to create a chain of proxies each time adding some extra functionality.

There are other features of Dynamic Decorator which can be used to enhance the preprocessing and postprocessing functionality. Please see the article Add Aspects to Object Using Dynamic Decorator for details.

The Dynamic Decorator has more features which can be used to enhance the preprocessing and postprocessing code.

Conclusion

The Dynamic Decorator is specifically designed to add additional functionality to objects. It is a lightweight and feature-rich tool to extend object functionality and to add aspects to objects. On the other hand, although the Castle DynamicProxy can be used to add aspects to objects, it suffers performance issue and has limited features to enhance the aspects.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Object-oriented (OO) is about "classes" not "objects". But I truly believe that "objects" deserve more our attentions. If you agree, read more on... Dynamic Object Programming (DOP), Component-Based Object Extender (CBO Extender), AOP Container and Dynamic Decorator Pattern.

Mobile development is not just another type of front end. The real challenge is actually in the back end: How to present meaningful information in time to mobile users with exponentially increased data flooding around? Here is my first mobile solution: SmartBars - Barcode Reader, Price Comparison and Coupons.

Gary lives in southeast Michigan. My first programming language is FORTRAN. For the last a few years, I have primarily focused on .NET technologies with Mobile Development as my newest interest.

Comments and Discussions

 
Questionbullshit Pin
EvilShrike1-Jul-11 6:45
EvilShrike1-Jul-11 6:45 
AnswerRe: bullshit Pin
Gary H Guo1-Jul-11 7:27
Gary H Guo1-Jul-11 7:27 
GeneralRe: bullsh*t Pin
EvilShrike1-Jul-11 7:43
EvilShrike1-Jul-11 7:43 
GeneralRe: bullsh*t Pin
EvilShrike1-Jul-11 7:50
EvilShrike1-Jul-11 7:50 
GeneralRe: bullsh*t Pin
Gary H Guo1-Jul-11 8:36
Gary H Guo1-Jul-11 8:36 
GeneralRe: bullsh*t Pin
EvilShrike1-Jul-11 9:20
EvilShrike1-Jul-11 9:20 
GeneralRe: bullsh*t Pin
Gary H Guo1-Jul-11 9:33
Gary H Guo1-Jul-11 9:33 
GeneralRe: bullsh*t Pin
EvilShrike1-Jul-11 9:39
EvilShrike1-Jul-11 9:39 
GeneralRe: bullsh*t Pin
Gary H Guo1-Jul-11 9:58
Gary H Guo1-Jul-11 9:58 
QuestionIn fact here is what I was doing, before I moved on Pin
Sacha Barber1-Jul-11 6:00
Sacha Barber1-Jul-11 6:00 
//******************************************************************
// Proxy
//******************************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
using System.Reflection;
using System.Diagnostics;
using System.ComponentModel;

namespace MefWithAspects.Aspects
{
    public class AspectProxy : RealProxy, IDisposable
    {
        public object RealInstance { get; private set; }

        public AspectProxy(object subject)
            : base(subject.GetType())
        {

            this.RealInstance = subject;
       

            //We have to attach our object to the proxy.  
            //This isn't an automagic thing, probably because it isn't always relevant.
            AttachServer((MarshalByRefObject)subject);
        }

      

 

        public void Dispose()
        {
            //For proper cleanup, let's detach our server. 
            DetachServer();
        }


        public override IMessage Invoke(IMessage msg)
        {
            ///the MethodCallMessageWrapper provides read/write access to the method call arguments. 
            MethodCallMessageWrapper mc = new MethodCallMessageWrapper((IMethodCallMessage)msg);
            ///This is the reflected method base of the called method. 
            MethodInfo mi = (MethodInfo)mc.MethodBase;
            ///This is the object we are proxying. 
            MarshalByRefObject owner = GetUnwrappedServer();
            ///Some basic initializations for later use 
            IMessage retval = null;
            object outVal = null;

            //check for custom attributes
            object[] attribs = mi.GetCustomAttributes(typeof(IContextBound), true);
            IEnumerable<IContextBound> enteredAttribs = (from x in attribs
                                                         where
                                                             ((IContextBound)x).ContextOperationsRequired.HasFlag(ContextOperations.OnEntered)
                                                         select (IContextBound)x);
            IEnumerable<IContextBound> exitingAttribs = (from x in attribs
                                                         where
                                                             ((IContextBound)x).ContextOperationsRequired.HasFlag(ContextOperations.OnExiting)
                                                         select (IContextBound)x);

            bool isMethodCallAllowed = true;


            //call the OnEntered based ones
            foreach (IContextBound contextBoundAttrib in enteredAttribs)
            {
                isMethodCallAllowed &= contextBoundAttrib.OnContextEntered(owner, mi, mc.Args);
            }

            //call the method
            if (isMethodCallAllowed)
            {
                outVal = mi.Invoke(owner, mc.Args);
                retval = new ReturnMessage(outVal, mc.Args, mc.Args.Length, mc.LogicalCallContext, mc);
            }
            else
            {
                retval = new ReturnMessage(outVal, mc.Args, mc.Args.Length, mc.LogicalCallContext, mc);
            }

            //call the OnExiting based ones
            foreach (IContextBound contextBoundAttrib in exitingAttribs)
            {
                contextBoundAttrib.OnContextExiting(owner, mi, mc.Args);
            }
            return retval;

        }


        public static Object Factory(object obj)
        {
            return new AspectProxy(obj).GetTransparentProxy();
        }
    }  
}








//******************************************************************
// Helper interface
//******************************************************************
using System;
using System.Reflection;

namespace MefWithAspects.Aspects
{
    [Flags]
    public enum ContextOperations
    {
        OnEntered = 0,
        OnExiting = 1
    }



    public interface IContextBound
    {
        /// <summary>
        /// This method should be used to carry out any on entered context logic that occurs when an apsect enabled
        /// objects method is entered. Since the method call has not occurred yet, you are able to return true/false
        /// depending on whether you wish the context method call to be able to run or not
        /// </summary>
        /// <param name="content">The object the aspect is applied to</param>
        /// <param name="method">The method being entered on the context object</param>
        /// <param name="args">The method arguments</param>
        /// <returns>Return true/false depending on whether you wish the context method call to run or not</returns>
        bool OnContextEntered(object content, MethodInfo method, Object[] args);

        /// <summary>
        /// This method should be used to carry out any on exiting context logic that occurs when an apsect enabled
        /// objects method is just about to exit. Since the method has already run there may not be anything to do 
        /// in this method
        /// </summary>
        /// <param name="content">The object the aspect is applied to</param>
        /// <param name="method">The method being entered on the context object</param>
        /// <param name="args">The method arguments</param>
        void OnContextExiting(object content, MethodInfo method, Object[] args);

        /// <summary>
        /// Specifies what <c>ContextOperations</c> the implmenting class supports
        /// </summary>
        ContextOperations ContextOperationsRequired { get; }
    }

}





//******************************************************************
// Logging apsect
//******************************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

using System.ComponentModel.Composition;
using log4net;

namespace MefWithAspects.Aspects
{
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false)]
    public class LoggerContextAttribute : Attribute, IContextBound
    {
        #region Data
        private static ILog logger;
        private ContextOperations contextOperationsRequired;
        #endregion

        #region Ctor
        public LoggerContextAttribute(ContextOperations contextOperationsRequired)
        {
            this.contextOperationsRequired = contextOperationsRequired;
        }
        #endregion

        #region Public Methods
        public static void SetLogger(ILog newLogger)
        {
            logger = newLogger;
        }
        #endregion

        #region IContextBound Members

        public bool OnContextEntered(object contextObject, MethodInfo method, Object[] args)
        {
            DoLogging("Enter", contextObject, method, args);
            return true; //Simply allows context to continue
        }

        public void OnContextExiting(object contextObject, MethodInfo method, Object[] args)
        {
            DoLogging("Exiting", contextObject, method, args);
        }

        public ContextOperations ContextOperationsRequired
        {
            get { return contextOperationsRequired; }
        }

        #endregion

        #region Private Methods
        private void DoLogging(string contextOperation, object contextObject, MethodInfo method, Object[] args)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine(String.Format("{0} {1} : {2}", DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString(), contextOperation));
            sb.AppendLine(String.Format("{0} {1} : {2}.{3}(..) called with the following parameters",
                DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString(), contextObject.GetType().Name, method.Name));
            int i = 0;
            foreach (ParameterInfo parameter in method.GetParameters())
            {
                sb.AppendLine(String.Format("{0} {1} : Name : {2}, Type: {3}, Value: {4}",
                    DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString(), parameter.Name, parameter.ParameterType, args[i]));
                i++;
            }
            logger.Debug(sb.ToString());
        }
        #endregion
    }
}



//******************************************************************
//Here is how I would create a aspect enabled class
//******************************************************************
AspectProxy.Factory(new MainWindowViewModel());






//******************************************************************
// Aspected class
//******************************************************************
public class MainWindowViewModel : MarshalByRefObject
    {
        public virtual int UniqueId { get; private set; }


        [LoggerContextAttribute(ContextOperations.OnEntered | ContextOperations.OnExiting)]
        public virtual int Age { get; set; }


        [LoggerContextAttribute(ContextOperations.OnEntered | ContextOperations.OnExiting)]
        public virtual void SetSomething<T>(T value, int ageValue)
        {
            T x = value;
            this.Age = ageValue;
            UniqueId = ++UniqueId;
        }




        public MainWindowViewModel()
        {
            Age = 1;
            UniqueId = ++UniqueId;
        }
    }





I basically allowed aspects to be defined through Attributes, and my proxy would look for these attributes and pass in the context (Method call). It did mean your objects were pretty neat and simply had a few extra attributes.

Like I say there were issues with Binding in WPF so I had to start using IL Weaving approach instead. Castles DynamicProxy did work for INPC but did not like certain other things, so had to abandon that too. IL Weaver seem logical next step.

I also wanted to use it with MEF too.


Hope that makes sense.
Sacha Barber
  • Microsoft Visual C# MVP 2008-2011
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

AnswerRe: In fact here is what I was doing, before I moved on Pin
Gary H Guo1-Jul-11 7:15
Gary H Guo1-Jul-11 7:15 
AnswerRe: In fact here is what I was doing, before I moved on Pin
Gary H Guo2-Sep-11 9:50
Gary H Guo2-Sep-11 9:50 
QuestionI like this and have been working on something quite similar using RealProxy [modified] Pin
Sacha Barber1-Jul-11 5:50
Sacha Barber1-Jul-11 5:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.