Many times I was asking myself what's better IoC container for this or that project. Their performance is the one side of choice. You can find the complete performance comparison here. Another side of the choice is the simplicity and speed to learn. So I decided to compare several containers from this perspective and took autofac, simple injector, Structure Map, ninject, Unity, Castle Windsor. It seems this list is the most relevant to what can be considered as the most popular dependency injection containers. You can find some of them in the list of top 20 NuGet packages and I added others by my preference. Personally, I very like Autofac and during working on this article I became firmly convinced that it's the best one in the most of practical cases.
This article describes the basics of dependency injection containers such as configuration and component registration. Next articles will compare lifetime scope management and advanced features. All code snippets can be found in the GitHub repository LifetimeScopesExamples.
Documentation Summary
During working on the article I learnt new dependency injection frameworks and good documentation helped me a lot. Unfortunately not every framework has a good one and I still needed to search for a solution in Google. This is just a summary after the article was created.
Quality | Comment | |
---|---|---|
Autofac | Excelent | All cases are described and there is no need to Google anything. It's easy to find a solution and examples are clean. |
Simple Injector | Good | Most of the cases are described in the documentation. But still several requests to Google were done. Although it was easy to find a solution. |
Structure Map | Middle | Not all cases are described. Registrations with expression, property and method injections are not described well. It was required to search on Google for solutions. |
Ninject | Poor | Not all cases are described. Registrations with expression, property and method injections are not described. It was required to search on Google for solutions and they were hard to be found. |
Unity | Bad | Despite a lot of text in documentation it's useless. All cases were searched on Google and not easy to find. |
Castle Windsor | Middle | Not all cases are described or have good examples. It was required to search on Google for solutions. |
The documentation for dependency injection frameworks:
- Autofac documentation
- Simple Injector documentation
- StructureMap documentation
- Ninject documentation
- Unity documentation
- Castle Windsor documentation
Configuration Summary
In this article I did'not study XML configuration. All code snippets describe common usages of the dependency injection containers by means of their fluent interface. Here you can find the following:
- Injection via Constructors, when dependencies are initialized via constructor parameters
- Injection via Properties, when dependencies are initialized via class properties
- Injection via Methods, when dependencies are initialized via special setter methods
- Registration via Expressions, when you can specify additional logic on creation
- Registration via Convention, when you can automatically register everything
- Registration via Modules, when you can specify a class which encapsulates configuration
The purpose of the article is to bring working examples for each of the cases. Such advanced scenarios as parameterized registrations are out of the scope.
Object Model and Test Scenario
In order to test IoC containers I created a simple model. There are several modifications of it to reflect requirements for property and method injection. Some of the dependency injection frameworks require using special attributes to allow injections to properties or methods. I explicitly noticed about that in the section about registration traits.
The test scenario create a container and gets an object from it two times in order to test lifetime scope management. The later one will be described in further articles.
Dependency Injection via Constructors
The configuration to inject via constructors does not require any special attributes or naming in its basic variant.
Autofac | |
Simple Injector | |
Structure Map | |
Ninject | |
Unity | |
Castle Windsor |
Dependency Injection via Properties
Some dependency injection containers require usage of special attributes which help to recognize properties to be initialized. I personally don't like this approach because the object model and IoC container becomes strongly tied. Ninject requires using of the attribute [Inject], Unity requires the attribute [Dependency]. At the same time Castle Windsor does not require anything to make properties initialized. They are injected by default.
Autofac | |
Simple Injector | It does not have implicit property injection. You can use registration via expressions to initialize properties on creation. |
Structure Map | |
Ninject | |
Unity | |
Castle Windsor |
Dependency Injection via Methods
Along with the property injection the method injection can help with circular references. From the other hand, this introduces another issue which should be avoided. In few words API does not provide any hint that the execution of such method is required to make some object fully initialized. Read more about temporal coupling.
As for the property injection some IoC containers require usage of special attributes with the same drawbacks. Ninject requires the attribute [Inject] for methods. Unity requires usage of the attribute [InjectionMethod]. All methods marked with such attributes will be executed when object is resolved.
Autofac | |
Simple Injector | |
Structure Map | |
Ninject | |
Unity | |
Castle Windsor |
Registration via Expressions
Most of the cases for the previous topic are nothing less than registration by means of lambda expressions or delegates. Such way of registration help you to add some logic to the moment when objects are created, but it is not a dynamic approach. You should use parameterized registration in order to dynamically initialize components when resolve them.
Autofac | |
Simple Injector | |
Structure Map | |
Ninject |
or |
Unity | |
Castle Windsor |
Ninject has differences between ToMethod and ToConstructor binding which is described here. In few words, when you use ToContructor you also may use conditional binding. The following statement won't work for ToMethod binding.
Registration via Conventions
In some cases you won't need to write a configuration code at all. The common scenario is as follows: scan an assembly for types, extract their interfaces and register them as interface-implementation pairs in the IoC container. It might be useful for really big projects, but maybe complicated for developers who is new for a project. There are several moment to remember.
Autofac registers all possible implementations and store them in the internal array. It uses a default one when resolve a dependency. The latest registrations will be used as per the documentation. For example, in the sample repositories with constructor initialization will be used. Simple Injector does not have any built-in method to scan and register implementation. You should do this manually. You can find the example below. Structure Map and Unity requires public implementation classes. Internal classes are not visible for their built-in scanners. Ninject requires additional nuget package Ninject.Extensions.Conventions. The source you can find on GitHub. It also requires public implementation classes as for the previous.
Autofac |
var builder = new ContainerBuilder(); builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces(); var container = builder.Build(); |
Simple Injector |
var container = new Container(); var repositoryAssembly = Assembly.GetExecutingAssembly(); var implementationTypes = from type in repositoryAssembly.GetTypes() where type.FullName.Contains("Repositories.Constructors") || type.GetInterfaces().Contains(typeof (ILog)) select type; var registrations = from type in implementationTypes select new { Service = type.GetInterfaces().Single(), Implementation = type }; foreach (var reg in registrations) container.Register(reg.Service, reg.Implementation); |
Structure Map |
var container = new Container(); container.Configure(c => c.Scan(x => { x.TheCallingAssembly(); x.RegisterConcreteTypesAgainstTheFirstInterface(); })); |
Ninject |
var container = new StandardKernel(); container.Bind(x => x.FromThisAssembly().SelectAllClasses().BindDefaultInterfaces()); |
Unity |
var container = new UnityContainer(); container.RegisterTypes( AllClasses.FromAssemblies(Assembly.GetExecutingAssembly()), WithMappings.FromAllInterfaces); |
Castle Windsor |
var container = new WindsorContainer(); container.Register(Classes.FromAssembly(Assembly.GetExecutingAssembly()) .IncludeNonPublicTypes() .Pick() .WithService.DefaultInterfaces()); |
Registration via Modules
Modules can help you to partition your dependency injection configuration. You can group them by context (data access, business objects) or by purpose (production, tests). Some IoC containers can scan assemblies to find such modules. In the article I described the basic way of using them.
Autofac | |
Simple Injector | It does not have anything like this. |
Structure Map | |
Ninject | |
Unity | |
Castle Windsor |
|
PS
I liked Windsor as well as Autofac though they have different default lifetime scope configuration. I'll describe that in the next article. Please, don't hesitate to propose changes to GitHub sample project or to this description. I'll reflect important moments as soon as possible.