I thought I’d write a bit about how I understand the philosophy of IoC containers. I realize I’m actually building up to something that I started talking about a while ago. I’m probably not saying anything that Martin Fowler didn’t say before, but I have my own slant on it. To start off with, I’d like to just review what we actually mean by various terms:
- Inversion of Control (IoC) is a general name for the pattern where an object isn’t responsible for managing the lifecycle of the services it uses.
- The simplest way to implement this (in .NET) is passing services in through the constructor. This is termed constructor injection.
- Typically, services are passed in using interfaces, which eases testability. However, Inversion of Control is not about testability.
So what is an IoC container? It’s a configuration tool. That’s it. Typically, it implements the constructor injection pattern like so:
- For each object registered, you usually specify:
- A name for the component
- The interface it implements
- The class that implements it.
- For primitive values, you just say what the constructor parameter is and what the value should be.
- For interfaces, you either not specify the implementation, in which case you get the default, or specify a particular component reference.
Actually, there is one other thing the container does: it handles lifecycles. This is a major feature that people often take for granted. The clue is in the name, really. Containers are things that hold objects, not produce them. Containers typically allow you to specify the lifecycle of the object e.g.
- one implementation in the process (Singleton)
- one implementation in the thread
- one implementation in an HttpContext
This lifecycle management is crucial to the use of IoC containers in most environments. The catch is that it can have side effects you do not expect. For instance, if you call a parameterized resolve on an object with a singleton lifecycle, the object will only ever have the first set of parameters passed in. Any others will be ignored (the moral of this story is to always use transient lifecycles when dealing with run-time parameters).
A fundamental part of the philosophy of IoC containers is that they should be extremely low footprint and non-invasive. The code should not need to know it is running in a container. Nor should the interfaces. There are, however, a number of times that you do need to know about the container. The obvious one is when reasoning about lifecycle management, however there are a number of times the abstraction gets broken. Having the abstraction broken is not as painful as having no abstraction at all, but it can be a distraction.
Evaluation of Containers
There are, of course, a lot of subtleties about containers. Quite a lot of people come to the conclusion that the libraries out there are too “heavy-weight” and that they would be better off rolling their own. If you’re one of those people. hopefully after reading this list you will either decide to refocus your efforts on improving the existing libraries, or you will have a USP that merits the duplication of effort. (Or you just want to have fun, which is always a valid reason for writing code.) I’ve listed some of them out here:
Most of this is specific to Castle Windsor, since its the one I’ve worked with most, but many of these questions are common across implementations and are things you should watch out for when evaluating. I will re-iterate that whilst it is easy to write a simple IoC container, writing a very good one such as Castle is a challenge.
Are Primitives Strings?
My personal bugbear is that IoC containers started out when XML was fashionable. As a consequence, there’s a tendency in most of them to treat everything as a string. Since these days there’s a move towards DSLs such as Binsor or fluent configuration, the requirement that parameters be strings is out of date. There are a number of side effects of this. Castle Windsor RC3, for instance, fails one of its unit tests in a UK environment due to different date formats. Equally, adding a primitive type that isn’t easily expressed as a string is painful. Custom Type Converters are a useful concept for dealing with text-based file formats, but seriously, why can’t you say
Component.For<IDownloader>() .ImplementedBy<Downloader>() .Parameters(
Parameter.ForKey("target").Eq(new Uri("http://www.google.com")) ) );
The current way of achieving this is unnecessarily verbose.
How are Lists Handled?
If there is one thing I heartily dislike about Castle, it’s the list handling. Ironically, in many ways, the list handling is strong: it’s relatively easy to register an array of array of strings, for instance. However, once you leave primitives, it gets more ambitious. If you create a constructor parameter of IEnumerable<IService>, it will by default pass in a list of all components that are registered with the IService interface. There are a number of problems with this
- The worst is that it gets in the way of the second simplest use case of a list: one where you specified a list of component references yourself. If you try this, you end up with a type conversion error.
- It can’t handle super-interfaces, it will only ever do exact matches.
- You can’t specify that you care about interfaces on the registered implementations. Thus, requesting IEnumerable<IDisposable> wouldn’t return the “Right Thing” (all registered disposable objects) even if you could specify that you wanted super-interfaces.
I would advise anyone evaluating a container to pay particular attention to how you can specify lists of components, because it come up a lot in real use cases.
What Issues are there with Open/Closed Generics?
There’s always a couple of bugs to do with open and closed generics. Castle recently fixed another one. In March of this year, it wasn’t possible to express this concept in StructureMap:
Component.For<IMessageHandler<string>>()
.ImplementedBy<MessageHandler>()
);
Indeed, this issue was pretty much why I moved to Castle in the first place. These days you’ve got to come up with something fairly involved to run into a problem (e.g. an open generic type relying on a closed one). However, if you’re using one of the many less-popular frameworks, or rolling your own, you need to watch out for this.
How does the Container Deal with Multiple Interfaces?
If you register the same class as the implementation of multiple interfaces, typically you will end up with multiple instances. It’s possible to mitigate this by using explicit component references, but that’s not a perfect solution. Sometimes you want a service that exposes different interfaces to different consumers. Castle Windsor calls this feature “forwarding”.
How can you Inject your own code?
How good is the container at handling the case where it doesn’t create the object itself? Can you say something like this?
Component.For<IConnection>()
.CreatedBy(() => ConnectionFactory.NewConnection() )
);
Windsor’s story here is rather painful, with two facilities defined which use reflection to run. On the other hand, they support Dynamic Proxy out of the box, so intercepting method calls to the interfaces is pretty simple and powerful.
Can you Create a Derived Container?
I am, frankly, amazed this does not come up more often. It should be relatively easy to create a container based upon another container, overriding and extending parts of the configuration. This is actually extremely useful. Binsor has the Extend keyword (you’ll need to check the unit tests for documentation) which achieves this, but frankly this is too important a feature to be left to the DSL, this should be a fundamental part of the container. Certainly there’s no easy way to achieve this without using Binsor in Windsor. I think there will probably be a whole separate post about this.