The sample code can be downloaded here
This week I found myself with some time on my hands, so I knocked up a simple object pool. It’s fairly standard stuff… you call Fetch to get something out of the pool, and Return to put it back. Fairly standard.
The problem
The call to Return felt a bit annoying. If we forget to call that, we end up leaking resources as we create more and more objects, or get an exception thrown at us. Not good. I wanted something like the using (whatever) syntax, which I think is the most awsome thing ever.
To do that, we’d have to ensure that all the items in the pool implement IDisposable, have a reference back to the pool, and call Return in their dispose method. This isn’t really feasible; for starters, it would tightly couple everything with the pool, which is, like, all sorts of bad. A far better approach is, in my opinion, to wrap the object being pooled to add whatever behaviour I need. Not necessarily a great idea if you don’t know what’s going to be pooled beforehand.
The solution
Luckily, the good folk of the Castle Project came to the rescue with their Dynamic Proxy API. This API is used by several great open source projects out there, so it comes in very well recommended.
Castle Dynamic Proxy create wrappers around your object. The wrapper intercepts calls to the object, allowing you to redirect them as needed. The first thing you’d need to set up is an Interceptor class; the thingy that will handle calls before they are passed to your object. In our case, the basic form of the interceptor would be dead simple.
Intercepting calls
First of all, the interceptor needs to know about the pool, so it can call methods on it. Passing it in via a constructor does the job:
private class CallInterceptor { private Pool<T> Parent { get; set; } public CallInterceptor(Pool<T> parent) { Parent = parent; } }
All run-of-the-mill so far. Next, we have to implement the Intercept method in IInterceptor
to get this thing to work:
private class CallInterceptor : IInterceptor
{
...
...
public void Intercept(IInvocation invocation)
{
if (invocation.Method != null && invocation.Method.Name.Equals("Dispose"))
Parent.Return((T)invocation.InvocationTarget);
else
Parent.Proceed();
}
}
That tells the proxy to check whether the call it just caught was bound for the Dispose method.
If that is the case, the pool’s Return method will be called with the invocation target as the
argument: in this case, the object being disposed.
All well and good, but what if the object needs to be disposed after used? Since we’re pooling,
we don’t want to dispose of the pooled object while the pool is still hanging around. That makes
things easier for us, as we just have to see if the pool is disposed, and Proceed invocations
to Dispose if it is. This variation is the one being used in the sample code.
Creating an instance of a proxy
To create a proxy in the pool, we need to create an instance of ProxyGenerator. The rest is dead
simple:
T proxy = proxyGenerator.CreateClassProxy<T>(interceptor);
Where interceptor is an instance of our interceptor class, above. Like I said, dead easy.
And the result
We can now use the following syntax with our pool:
using (MyPooledResource r = pool.Fetch())
{
// Do stuff with the resource.
}
There are obviously some restrictions to this method. The pooled class needs to implement
IDisposable, and the Dispose method needs to be marked virtual so that Castle can create
the proxy to it. The last point is important; if it’s not virtual, it will compile fine,
but your application will then call the method directly, not the proxy. Boom.
As you can see in the sample code, the generic type parameter must have a default constructor,
but this is a requirement of the Pool, not of the dynamic proxy API.
Since we’re adding another layer, there will also be a slight performance hit.
TODO
This was my first brush with dynamic proxies, and I have to say it looks real interesting. It’s
probably more useful when writing generic APIs than actual applications; you can work out some
really clean APIs this way. I’ll need to explore it further though, as I expect there are more
efficient ways to use it than the simple example presented here.