Please note that as of Release 2.3 of SimpleInjector the features discussed in this and related articles are available out of the box with the new registration method RegisterAllOpenGeneric
I like SimpleInjector, it’s great and so is the support. The guy behind SimpleInjector is a top class craftsman.
However I recently discovered that SimpleInjector does not return any open generic types when asked for IEnumerable<ISomeService<T>>
. And it’s a feature I’d really like to have.
The first indication that SimpleInjector will not be able to return any open generic instances of ISomeService<T>
when a request is made for IEnumerable<ISomeService<T>>
is in the extension method used for registering a collection of implementations:
container.RegisterManyForOpenGeneric(typeof(ISomeService<T>), AccessibilityOption.PublicTypesOnly, (serviceType, implTypes) => container.RegisterAll(serviceType, implTypes), AppDomain.CurrentDomain.GetAssemblies() );
which states:
Allows registration of all concrete, non-generic types with the given accessibility that are located in the given set of assemblies that implement the given openGenericServiceType, by supplying a SimpleInjector.Extensions.BatchRegistrationCallback delegate, that will be called for each found closed generic implementation of the given openGenericServiceType.
“concrete, non-generic types” :-(
So I compared this aspect of SimpleInjector with some other IoC containers (Autofac, Ninject, StructureMap and Castle).
I defined some test interfaces and classes
public interface IClass { } public interface IObserve<T> where T : class { } public class Class1 : IClass { } public class Class2 : IClass { } public class ObserveClass1 : IObserve<Class1> { } public class ObserveClass2 : IObserve<Class2> { } public class ObserveIObserve<TCommand> : IObserve<TCommand> where TCommand : class, IClass { }
public class InjectionTestClass1 { private readonly IEnumerable<IObserve<Class1>> _v; public InjectionTestClass1(IEnumerable<IObserve<Class1>> v) { _v = v; } public int HandlerCount { get { return _v.Count(); } } } //test making a request for a single registration where we have more than one //I expect this code to fail public class InjectionTestClass2 { private readonly IObserve<Class2> _v; public InjectionTestClass2(IObserve<Class2> v) { _v = v; } }
What I expect to happen based on the above classes is
InjectionTestClass1
has a count of 2 implementations injected into itInjectionTestClass2
fails due to duplicate registrations
So what does Autofac do?
Bootstrap code
private IContainer BootstrapAutofac() { ContainerBuilder builder = new ContainerBuilder(); builder .RegisterAssemblyTypes(AppDomain.CurrentDomain.GetAssemblies()) .AsImplementedInterfaces(); builder .RegisterAssemblyTypes(AppDomain.CurrentDomain.GetAssemblies()) .AsClosedTypesOf(typeof(IObserve<>)); builder .RegisterAssemblyTypes(AppDomain.CurrentDomain.GetAssemblies()) .AsSelf(); return builder.Build(); }
Test code
IContainer autofac = BootstrapAutofac(); var tester1 = autofac.Resolve<InjectionTestClass1>(); int count1 = tester1.HandlerCount; var tester1b = autofac.Resolve<InjectionTestClass2>();
Results
InjectionTestClass1
has 2 implementations injected – perfectInjectionTestClass2
creates – not so good
So what does Ninject do?
Bootstrap code
private IKernel BootstrapNinject() { IKernel kernel = new StandardKernel(); kernel.Bind(scanner => scanner.FromThisAssembly().SelectAllClasses().BindAllInterfaces()); return kernel; }
Test Code
IKernel ninject = BootstrapNinject(); var tester2 = ninject.Get<InjectionTestClass1>(); int count2 = tester2.HandlerCount; var tester2b = ninject.Get<InjectionTestClass2>();
Results
InjectionTestClass1
has 2 implementations injected – perfectInjectionTestClass2
creates – not so good
So what does StructureMap do?
Bootstrap code
private IContainer BootstrapStructureMap() { Registry registry = new Registry(); registry.Scan(scanner => { scanner.TheCallingAssembly(); scanner.AddAllTypesOf(typeof(IObserve<>)); scanner.WithDefaultConventions(); scanner.ConnectImplementationsToTypesClosing(typeof(IObserve<>)); }); Container container = new Container(registry); return container; }
Test code
IContainer structureMap = BootstrapStructureMap(); var tester4 = structureMap.GetInstance<InjectionTestClass1>(); int count4 = tester4.HandlerCount; var tester4b = structureMap.GetInstance<InjectionTestClass2>();
Results
InjectionTestClass1
has 2 implementations injected – perfectInjectionTestClass2
fails – perfect
So what does Castle Windsor do?
Bootstrap code
private static WindsorContainer BootstrapCastle() { WindsorContainer container = new WindsorContainer(); IKernel kernel = container.Kernel; kernel.Resolver.AddSubResolver(new CollectionResolver(kernel)); container.Register( Classes .FromThisAssembly() .Pick() .LifestyleTransient() .WithServiceAllInterfaces()); return container; }
Test code
WindsorContainer castle = BootstrapCastle(); var tester5 = castle.Resolve<InjectionTestClass1>(); int count5 = tester5.HandlerCount; var tester5b = castle.Resolve<InjectionTestClass2>(); return count5;
Results
InjectionTestClass1
has 2 implementations injected – perfectInjectionTestClass2
creates – not so good
So what does SimpleInjector do?
Bootstrap code
private static Container BootstrapSI() { Container container = new Container(); container.RegisterManyForOpenGeneric(typeof(IObserve<>), AccessibilityOption.PublicTypesOnly, (service, implTypes) => container.RegisterAll(service, implTypes), AppDomain.CurrentDomain.GetAssemblies() ); container.Verify(); return container; }
Test code
Container si = BootstrapSI(); var tester3 = si.GetInstance<InjectionTestClass1>(); int count3 = tester3.HandlerCount; var tester3b = si.GetInstance<InjectionTestClass2>();
Results
InjectionTestClass1
has 1 implementation injected – not so goodInjectionTestClass2
fails – there’s only one implementation so this is not so good? maybe, see the summary
Summary
From these tests we can conclude
Autofac
- Returns a mixture of open and closed generics when asked for all implementations of a particular service
- Returns an instance of an implementation when it has multiple options
Ninject
- Returns a mixture of open and closed generics when asked for all implementations of a particular service
- Returns an instance of an implementation when it has multiple options
StructureMap
- Returns a mixture of open and closed generics when asked for all implementations of a particular service
- Fails with an exception when a request for a single implementation has multiple options
Castle Windsor
- Returns a mixture of open and closed generics when asked for all implementations of a particular service
- Returns an instance of an implementation when it has multiple options
SimpleInjector
- Returns any closed generics when asked for all implementations of a particular service
- Fails when asked for a single registration even when a collection of one registration has been made matching the requested service. I.e. SimpleInjector handles single and multiple registrations separately
At this point we can conclude two things. Some mainstream IoC containers support returning a mixture of open and closed generics when asked for all implementations of a particular service.
A subset of these containers do not enforce registration integrity and will allow for duplicate registrations.
2 out of 2 for StructureMap; 1 out of 2 for SimpleInjector; 1 out of 2 for Autofac, Ninject and Castle.