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 of 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.
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;
}
}
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;
}
}
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();
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");
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");
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");
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
.
During the execution of the above code, you will see the following output:
First, let's take a look at the performance of each tool. For doing exactly the 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 truly 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 the 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 the 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 should 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 truly 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.
History
- 26th July, 2011: Initial post