Castle Dynamic Proxy tutorial part XV: Patterns and Antipatterns

This is part fifteen of my tutorial on Castle Dynamic Proxy.

We’ve covered almost all of Dynamic Proxy. If you followed along through this series, you now know 95% of Dynamic Proxy 2.1 features that get used 99,9% of the time. Now is the time to wrap up, and with that we’ll review some of the most common pitfalls that you may encounter when developing code on top of Dynamic Proxy.

Leaking this

Consider this simple interface/class pair

public interface IFoo
{
	IFoo Bar();
}

public class Foo : IFoo
{
	public IFoo Bar()
	{
		return this;
	}
}

Now, let’s say we create a proxy for IFoo with target and use it like this:

var foo = GetFoo(); // returns proxy
var bar = foo.Bar();
bar.Bar();

Can you see the bug here? The second call is performed not on a proxy but on a target object itself! Our proxy is leaking its target.

This issue obviously does not affect class proxies (since in that case proxy and target are the same object). Why does not Dynamic Proxy handle this scenario on its own? Because there’s no general easy way to handle this. The example I showed is the most trivial one, but proxied object can leak this in a myriad of different ways. It can leak it as a property of returned object, it can leak it as sender argument of raised event, it can assign this to some global variable, it can pass itself to a method on one of its own arguments etc. Dynamic Proxy can’t predict any of these, nor should it.

In some of these cases there is often not much you can do about it, and its good to know that problem like this exist, and understand its consequences. In other cases though, fixing the issue is very simple indeed.

public class LeakingThisInterceptor:IInterceptor
{
	public void Intercept(IInvocation invocation)
	{
		invocation.Proceed();
		if(invocation.ReturnValue == invocation.InvocationTarget)
		{
			invocation.ReturnValue = invocation.Proxy;
		}
	}
}

You add an interceptor (put it as last one in the interceptors pipeline), that switches the leaking target back to proxy instance. It’s as simple as that. Notice that this interceptor is targeted specifically at the scenario from our example above (target leaking via return value). For each case you will need a dedicated interceptor.

Override equality

One of the most common mistakes when it comes to Dynamic Proxy is not overriding Equals/GetHashCode methods on proxy generation hooks and interceptor selectors, which means you’re giving up caching and that in turn coupled with bugs in BCL means performance hit (plus increased memory consumption).

Solution is very simple, and there’s no exceptions to this rule – always override Equals/GetHashCode methods on all your classes implementing either IProxyGenerationHook or IInterceptorSelector.

As of Dynamic Proxy 2.5 IInterceptorSelector implementations need not override Equals/GetHashCode for the caching to work, since changed proxy generation algorithm now only cares whether selector is present or not.

Make your Proxy Generation Hooks purely functional

Pure function, is a function that for given set of inputs always returns the same output. In case of proxy generation hook, it means that two equal (as specified by overriden Equals/GetHashCode methods) proxy generation hooks will for given type to proxy return the same values from their methods, and when asked again about the same type will again return the same values/throw the same exceptions.

This is a major assumption that Dynamic Proxy makes, and that’s what makes the caching mechanism work. If proxy generation hook is equal to the one already used to generate a proxy type, Dynamic Proxy will assume it would return the same values as the other one, which would result in identical proxy type, so it cuts through the generation process and returns the existing proxy type.

Make your supporting classes serializable

If you’re going to be serializing your proxies, you should make all the classes that go with it serializable. That includes proxy generation hooks, interceptors and interceptor selectors. Otherwise you will get an exception when trying to serialize your proxies. It is not mandatory, but I find it useful. Notice that you will need this also when persisting your proxy assembly to disk.

Use ProxyGenerationHooks and InterceptorSelectors for fine grained control

Do your interceptor’s methods look like this?

public void Intercept(IInvocation invocation)
{
	if(invocation.TargetType!=typeof(Foo))
	{
		invocation.Proceed();
		return;
	}
	if(invocation.Method.Name!="Bar")
	{
		invocation.Proceed();
		return;
	}
	if(invocation.Method.GetParameters().Length!=3)
	{
		invocation.Proceed();
		return;
	}
	DoSomeActualWork(invocation);
}

If they do this often means you’re doing something wrong. Move the decisions to proxy generation hook and interceptor selector

  • Do I ever want to intercept this method? If the answer is no, use proxy generation hook to filter it out of methods to proxy.

Notice that due to bug in Dynamic Proxy 2.1, if you choose not to proxy method on interface proxy, you will get an exception. Workaround for this is to say you want to intercept the method, and then use interceptor selector to return no interceptors for the method. This bug is fixed in Dynamic Proxy 2.2

  • If I do want to intercept this method, which interceptors do I want to use? Do I need all of them? Do I need just a single one? Use interceptor selector to control this.

On the other hand, remember that as every feature this one is also a double edged sword. Too liberal use of proxy generation hooks and interceptor selectors may greatly decrease efficiency of proxy type caching, which may hurt your performance. As always think how much control you need and what the implications on caching will be. Sometimes single if on top of your interceptor is lesser evil than increasing number of proxies required tenfold. As always – use the profiler in scenarios that mimic your production scenarios as closely as possible to check which option is the best for you.

SRP applies to interceptors

SRP stands for Single Responsibility Principle, which means that a class should do just one thing. Many people seem to forget about it when it comes to interceptors. They create one monstrous interceptor class that tries to do all the things they need from Dynamic Proxy – logging, security checking, parameter verification, augmenting target objects with behavior and many more.

Remember that Dynamic Proxy lets you have many interceptors per method call. Use this ability to split behavior between interceptors. You may end up with some general purpose interceptors for things like logging that you use for each intercepted method on each class. As long as all it does is logging – that’s ok.

You may end up with some interceptors that are used for methods on just some classes, like classes inheriting from common base class. As long as these interceptors do just one thing – that’s fine.

You may end up with some interceptors that exist solely for the purpose of intercepting just a single method on specific class or interface. That also is fine. Use interceptor selectors to match interceptors to their respective targets, and don’t be afraid to have multiple interceptors per method.

Technorati Tags: ,

Comments

James says:

Hi Krzysztof, many thanks for the great introduction. I’m new to Dynamic Proxy but have used the other Castle frameworks and I can’t really see why I would use this instead of an AOP framework.

What does Dynamic Proxy offer that AOP doesn’t?

Cheers!

@James

I think you’re misunderstanding terms here. AOP and DP are not mutually exclusive. AOP is a technique for tackling certain kinds of problems. DP is an implementation pattern. More than that – DP is a way (one of many) to implement AOP in your application (I’m doing that right now in the project I work on).

You can read about different ways to do AOP in this post by Ayende: ayende.com/…/7-Approaches-for-AOP-in-.Net.aspx
As you can see DP is on the list.

Where it shines as compared to other frameworks, is that its execution is deferred, so you can make decisions right on the spot when your program is already running.

Hope that answers your questions.