SimpleInjector IEnumerable<ISomeService<T>> with a mixture of open & closed generics – the problem

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

  1. InjectionTestClass1 has a count of 2 implementations injected into it
  2. InjectionTestClass2 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

  1. InjectionTestClass1 has 2 implementations injected – perfect
  2. InjectionTestClass2 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

  1. InjectionTestClass1 has 2 implementations injected – perfect
  2. InjectionTestClass2 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

  1. InjectionTestClass1 has 2 implementations injected – perfect
  2. InjectionTestClass2 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

  1. InjectionTestClass1 has 2 implementations injected – perfect
  2. InjectionTestClass2 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

  1. InjectionTestClass1 has 1 implementation injected – not so good
  2. InjectionTestClass2 fails – there’s only one implementation so this is not so good? maybe, see the summary

Summary

From these tests we can conclude

Autofac

  1. Returns a mixture of open and closed generics when asked for all implementations of a particular service
  2. Returns an instance of an implementation when it has multiple options

Ninject

  1. Returns a mixture of open and closed generics when asked for all implementations of a particular service
  2. Returns an instance of an implementation when it has multiple options

StructureMap

  1. Returns a mixture of open and closed generics when asked for all implementations of a particular service
  2. Fails with an exception when a request for a single implementation has multiple options

Castle Windsor

  1. Returns a mixture of open and closed generics when asked for all implementations of a particular service
  2. Returns an instance of an implementation when it has multiple options

SimpleInjector

  1. Returns any closed generics when asked for all implementations of a particular service
  2. 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.