Comparing .NET DI (IoC) Frameworks, Part 1
August 19th, 2008
Having some spare time and interest, I decided to learn a bit more about the features of modern .NET IoC frameworks. Being aware of what’s available always helps, and I haven’t yet seen a comprehensive feature comparison.
For the comparison, I have selected the frameworks that I feel to be most documented and popular:
If you feel that your framework should be here, just send me the values for all feature columns, and I’ll add it.
0. Terminology
The first thing I learned is that common concepts are sometimes named differently between the frameworks.
So I think it makes sense to start with some terminology.
Components are classes registered within the container. They can be instantiated by the container to satisfy service dependencies of other components. Services are what components provides and depends on.
In most of the cases services are interfaces, and components are classes implementing them.
Autowiring is an automatic detection of dependency injection points. If the framework is able to match the constructor parameter types with registered services and provide these services, it is an example of autowiring.
Transient components, as compared to singletons, are created each time they are requested from container. The exact term used for transients differs quite much between frameworks.
Automatic registration is a way to automatically detect and register components within an assembly (or larger environment). It can also be called batch registration.
1. Licensing and core size
All reviewed frameworks work under .NET 2.0, and have liberal licenses allowing commercial and open-source usage.
The first table lists the reviewed frameworks with some basic parameters.
The color map is easy: green is great, yellow is ok, red is bad. All colors are extremely subjective, things bad for me may mean that I am doing something wrong.
I do not feel library count or size to be important for developments, so no red points this time.
Ninject is the most interesting case, since it has a very naive auto-wiring implementation by default. However, it has an official Ninject.Extensions.AutoWiring library, so in all following tables I included its features in Ninject functionality.
2. Container configuration
The first thing to do with container is to register something in it. So let’s review the configuration options provided by various framework.
As you can see, all frameworks support programmatic registration. Even better, most of them have fluent interfaces for registration API. Spring.Net and Castle are behind others in this regard, since Spring.Net API is somewhat cumbersome and not fluent, while Castle does not support fluent registration in RC3.
As I noted before, automatic registration is a way to register all types in given set of assemblies/namespaces based on attributes or other conventions. I find it very useful, since the convention-driven approach saves time on configuration and lessens a chance of a configuration error.
Castle’s BatchRegistrationFacility is obsolete and well-hidden, also it uses GetExportedTypes instead of GetTypes (The Sin of Shallow Digging), so it’s useless if you want to hide implementations and expose only interfaces. For Spring.NET I didn’t find an easy way, but it still might be possible, so you can correct me on this one.
Some people consider attributes to be POCO, some not, and I tend to agree with the latter when we are talking about DI frameworks. So no red points or yellow points if framework has no attributes, this column just for your information.
As for XML, I have a very strong opinion — only if actually needed, only as much as actually needed. Fortunately, no one of reviewed frameworks required XML (while most of them support it when needed). Spring.Net is somewhat differently oriented for this question, since its XML API is a better, more documented and encouraged approach. However, since Spring is a Java port, the approach is probably a compatibility heritage.
3. Basic injection/autowiring
The second thing after configuring the framework is knowing what exactly can be automatically injected. And, as I explained earlier, autowiring is just a name for framework-discovered injection points.
Basic constructor injection works great in all frameworks. For property injection Unity and StructureMap require explicit opt-in and treat the dependency as mandatory, which is a bit counterintuitive and does not provide any benefit over constructor injection.
As for multiple constructors, I really like the Castle/Autofac choice of constructor with most resolvable parameters. This is precisely what the human programmer will do, given a number of constructors and dependencies. With approach taken by Unity and StructureMap, it is harder to keep object DI-ignorant — you have to think whether your most-parameter constructor is resolvable, add DI-specific attributes or specify the constructor explicitly in the XML/code configuration.
The most interesting behavior was shown by the Spring.Net — it selected default (no-parameter) constructor first, and only if default did not exists, Spring went to the most resolvable.
Recursive dependency resolution is, in my opinion, one of the most important things in a DI framework. In a large project, when you have a lot of components and services, solving recursion easily is a must. In my tests only Castle and Autofac gave a useful error message, all other frameworks just crashed with unrecoverable StackOverflowException, which is a bad, bad thing to get in automatic test runner.
4. To be continued
In the second post of the series, I will review advanced features of DI containers (with StructureMap demonstrating unexpected feature), and show an automated feature tests I used to fill the blanks in these tables.