Search code examples
c#simple-injector

Auto-registration of generic classes


I'm trying to inject a generic class with Simple Injector. However, it fails with

SimpleInjector.ActivationException: No registration for type ... could be found

Here's the code to reproduce it:

using System;

namespace ErrorReproduction
{
    public class GenericClass<T>
    {
        public T Value { get; set; }
    }

    public interface IGenericInterface<T>
    {
        void DoSomething(T value);
    }

    public class NonGenericService : IGenericInterface<string>
    {
        public void DoSomething(string value) =>
            Console.WriteLine($"NonGeneric DoSomething (string): {value}");
    }

    public class GenericService<T> : IGenericInterface<GenericClass<T>>
    {
        public virtual void DoSomething(GenericClass<T> v) =>
            Console.WriteLine(
                $"Generic DoSomething {v.Value.GetType().Name}: {v.Value}");
    }

    /*
    public class StringService : GenericService<string>
    {
    }
    */

    public static class Test
    {
        public static void Main(string[] args)
        {
            var container = new SimpleInjector.Container();
            container.RegisterSingleton(
                typeof(IGenericInterface<>),
                typeof(IGenericInterface<>).Assembly);

            // works
            container.GetInstance<IGenericInterface<string>>()
                .DoSomething("non-generic-test");

            // breaks without StringService (No registration for type
            // IGenericInterface<GenericClass<string>>)
            container.GetInstance<IGenericInterface<GenericClass<string>>>()
                .DoSomething(
                    new GenericClass<string> {Value = "di-generic-test"});
        }
    }
}

If I comment-in the StringService above, it works. However, without it I receive the following exception:

Unhandled exception. SimpleInjector.ActivationException: No registration for type IGenericInterface<GenericClass<string>> could be found. at ErrorReproduction.Test.Main(String[] args) in ErrorReproduction/Test.cs:line 45

If I create a specific implementation that itself is not generic anymore, DI works. However, even without the specific one, the GenericService<T>, which is not abstract, is enough and should be injectable.

I'd like to not have to create a specific class for each case of GenericService<T> that we will have. They would just be empty like the above StringService, fully relying on the implementation of the generic service.

Is there a way to get the DI working for a case like this?

Edit: Turned out my use case is not exactly what I described above. However, the comments and answer provided got me on the right track. In the end, this is what worked for me:

container.RegisterSingleton(typeof(IMapper<,>), typeof(IMapper<,>).Assembly);
container.RegisterConditional(typeof(IMapper<,>),
    typeof(SimpleTimedValueProxyMapper<>),
    Lifestyle.Singleton,
    ctx => !ctx.Handled);
container.RegisterConditional(typeof(IMapper<,>),
    typeof(TimedValueProxyMapper<,>),
    Lifestyle.Singleton,
    ctx => !ctx.Handled);

Which, given the classes in the first example, would probably resolve to this (have not tested it):

container.RegisterSingleton(typeof(IGenericInterface<>), typeof(IGenericInterface<>).Assembly);
container.RegisterConditional(typeof(IGenericInterface<>),
    typeof(GenericService<>),
    Lifestyle.Singleton,
    ctx => !ctx.Handled);

Solution

  • The XML documentation of RegisterXXX(Type, Assembly[]) states the following (emphasis mine):

    Registers all concrete, non-generic, public and internal types in the given set of assemblies

    In other words, by default, only non-generic types are registered; your GenericService<T> is skipped in this auto-registration process.

    This is by design, and done so because generic types often need special handling. That's because they can easily overlap and their order of registration might be important.

    So to fix this, you can do the following:

    container.RegisterSingleton(
        typeof(IGenericInterface<>),
        typeof(IGenericInterface<>).Assembly);
    
    container.RegisterSingleton(
        typeof(IGenericInterface<>), typeof(GenericService<>));
    

    When there are many generic types, and they don't overlap with one another, you can instruct Simple Injector to also include generic type definitions in the registration. This can be done as follows:

    container.Register(
        typeof(IGenericInterface<>),
        container.GetTypesToRegister(
            typeof(IGenericInterface<>),
            new[] { typeof(IGenericInterface<>).Assembly },
            new TypesToRegisterOptions { IncludeGenericTypeDefinitions = true }));