This is part twelve of my tutorial on Castle Dynamic Proxy.
If you’ve been following the tutorial, you should remember that Castle Dynamic Proxy provides proxying capabilities by generating types at runtime. Dynamic code generation is not a lightweight operation, so pretty important aspect of Dynamic Proxy is its caching mechanism which we’ll going to cover in this post.
Let’s consider the following piece of a test:
var proxy1 = generator.CreateClassProxy<Foo>(new FooInterceptor()); var proxy2 = generator.CreateClassProxy<Foo>(new BarInterceptor(), new FooInterceptor()); Assert.AreEqual(proxy1.GetType(), proxy2.GetType());
Will it succeed? The answer is – yes. In this simple case both proxies would be semantically identical, so generator (or more precisely IProxyBuilder that the generator is using) caches the type that has been used during the first call. During the second call it checks the cache to see if a type meeting it’s criteria already exists, it finds it, so it skips the generation part and moves straight into creating the instance.
What is taken into consideration when Dynamic Proxy decides if a type can be reused or not?
- proxy target type obviously. We were creating two proxies for Foo, so the type could be reused. if the other type was proxy for Bar, it would not be able to reuse proxy of Foo, so a new type would be created.
- additional interfaces to proxy. If we decided that the second proxy should also implement an IBar the first type would not be reused, since it would not implement the interface, so a new type would be created.
- type of proxy target object. If we were creating interface proxy with target, and one implementation would be of type Foo, and the other of type Bar, a new type would be created. Does this feel counterintuitive? We’re creating interface proxy so why should implementer type be taken into account, right? Well, recall from the previous part, that additional interfaces are implemented differently depending on whether the target object implements them or not, hence it is important and proxy type could not be reused among different ones.
- proxy generation options. They obviously affect proxy generation so it’s natural that when they differ a new type needs to be created. Here’s which elements of proxy generation options affect caching
- proxy generation hook. They affect which methods get proxies, and which not, so if for two proxy generation options hooks are different, cached type will not be reused.
- mixin types. Logical, same types, cached type can be reused.
- interceptor selector. Newer versions of DynamicProxy (2.5+) only care if it’s present or not. Older versions look at Equals/GetHashCode
- base type for interface proxy. The default is System.Object. However if you changed it, a new type would have to be generated for a new proxy obviously.
As you can see there are quite a few ingredients that affect caching. There are few things you can make to improve your usage of cached types and decrease number of types that would have to be generated.
When you provide proxy generation hook, (or interceptor selector if you’re using Dynamic Proxy older than 2.5) always override Equals and GetHashCode methods. The default implementation compare referential equality which in most cases is not what you’d want. Two selectors, or hooks may expose exactly the same behavior, but if the type does not override equality methods to tell that fact to the outside world they will be considered different.
If instead of the code above we had this:
var proxy1 = generator1.CreateClassProxy<Foo>(new FooInterceptor()); var proxy2 = generator2.CreateClassProxy<Foo>(new BarInterceptor(), new FooInterceptor()); Assert.AreEqual(proxy1.GetType(), proxy2.GetType());
Notice we now generate each proxy with different generator, would the types be the same? The answer is – it depends.
As I said, generator uses IProxyBuilder which provides it with proxy types. Builder uses ModuleScope which manages the cache. So if the two generators had the same ModuleScope behind them the answer would be yet – we would get the same type. However if they had different scope each generator would produce its own type.
This is important to understand, but in real life you’ll probably need just one proxy generator anyway, hence all the proxies would be created in the same scope, which usually is the best option.