Registering service that implements several interfaces as singleton using open generic type

2

There is a generic MyClass<T> that implements 2 generic interfaces IMyInterface1<T> and IMyInterface2<T>. I want to register it as a singleton. So for a given T both interfaces will be resolved to the same instance of MyClass.

I'm trying to register it as open generic type with forwarding to a factory method:

services.AddSingleton(typeof(MyClass<>));
services.AddSingleton(typeof(IMyInterface1<>), sp => sp.GetRequiredService(typeof(MyClass<>)));
services.AddSingleton(typeof(IMyInterface2<>), sp => sp.GetRequiredService(typeof(MyClass<>)));

but then in runtime I'm getting exception with message "Open generic service type 'IMyInterface1`1[T]' requires registering an open generic implementation type."

When I'm registering my singleton for every concrete type T, i.e. without open generic type, all works fine:

services.AddSingleton(typeof(MyClass<A>));
services.AddSingleton(typeof(IMyInterface1<A>), sp => sp.GetRequiredService(typeof(MyClass<A>)));
services.AddSingleton(typeof(IMyInterface2<A>), sp => sp.GetRequiredService(typeof(MyClass<A>)));
services.AddSingleton(typeof(MyClass<B>));
services.AddSingleton(typeof(IMyInterface1<B>), sp => sp.GetRequiredService(typeof(MyClass<B>)));
services.AddSingleton(typeof(IMyInterface2<B>), sp => sp.GetRequiredService(typeof(MyClass<B>)));

I know that there is another way to register a singleton service with two interfaces - by providing implementation instance instead of implementation factory. But I can't use this in "real world" because my real class also has a lot of dependencies and it is probably impossible to resolve all of them at registration stage.

Is there a way to solve this issue using open generic types?


Detailed exception:

System.ArgumentException
  HResult=0x80070057
  Message=Open generic service type 'IMyInterface1`1[T]' requires registering an open generic implementation type. (Parameter 'descriptors')
  Source=Microsoft.Extensions.DependencyInjection
  StackTrace:
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.Populate()
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory..ctor(IEnumerable`1 descriptors)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine..ctor(IEnumerable`1 serviceDescriptors, IServiceProviderEngineCallback callback)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CompiledServiceProviderEngine..ctor(IEnumerable`1 serviceDescriptors, IServiceProviderEngineCallback callback)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services)
   at WebApplication1.Startup.ConfigureServices(IServiceCollection services) in ...\WebApplication1\WebApplication1\Startup.cs:line 39
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.InvokeCore(Object instance, IServiceCollection services)
   at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.<>c__DisplayClass9_0.<Invoke>g__Startup|0(IServiceCollection serviceCollection)
   at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.Invoke(Object instance, IServiceCollection services)
   at Microsoft.AspNetCore.Hosting.ConfigureServicesBuilder.<>c__DisplayClass8_0.<Build>b__0(IServiceCollection services)
   at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services)
   at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass12_0.<UseStartup>b__0(HostBuilderContext context, IServiceCollection services)
   at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
   at Microsoft.Extensions.Hosting.HostBuilder.Build()
   at WebApplication1.Program.Main(String[] args) in ...\WebApplication1\WebApplication1\Program.cs:line 16
c#
asp.net-core
dependency-injection

1 Answer

2

It looks like you can't do this - it's not supported.

The following issues look to have already requested this:

If you replace sp => sp.GetRequiredService(typeof(MyClass<A>)) on the interface registrations with typeof(MyClass<A>) then you will end up with a singleton instance for each interface you've registered, which doesn't sound like what you want.

The best I can think of, and it's not particularly pretty, is that you turn MyClass<T> into a facade that manages its own singleton instance of the underlying implementation. You could do this by having the facade call into a non-generic class with a static dictionary (static members are per-generic type so this needs to be a non-generic class) to retrieve a truly singleton instance per T.

Example

Register your services:

services.AddSingleton(typeof(MyFacadeClass<>));
services.AddSingleton(typeof(IMyInterface1<>), typeof(MyFacadeClass<>));
services.AddSingleton(typeof(IMyInterface2<>), typeof(MyFacadeClass<>));

Facade implementation:

public class MyImplementationClass<T> : IMyInterface1<T>, IMyInterface2<T>
{
    public void Interface1Method()
    {
    }

    public void Interface2Method()
    {
    }
}

public class MyClassSingletonRegister
{
    public static Dictionary<Type, object> MyImplementationClassInstances = new Dictionary<Type, object>();
}

public class MyFacadeClass<T> : IMyInterface1<T>, IMyInterface2<T>
{
    private readonly MyImplementationClass<T> _instance;

    public MyFacadeClass()
    {
        _instance = GetInstance();
    }

    private MyImplementationClass<T> GetInstance()
    {
        if (!MyClassSingletonRegister.MyImplementationClassInstances.TryGetValue(typeof(T), out var instance))
        {
            Debug.WriteLine("Creating new instance for " + typeof(T).Name);
            var myImplementationClass = new MyImplementationClass<T>();
            MyClassSingletonRegister.MyImplementationClassInstances.Add(typeof(T), myImplementationClass);

            instance = myImplementationClass;
        }

        return (MyImplementationClass<T>) instance;
    }


    public void Interface1Method()
    {
        _instance.Interface1Method();
    }

    public void Interface2Method()
    {
        _instance.Interface2Method();
    }
}

public interface IMyInterface1<T>
{
    void Interface1Method();
}

public interface IMyInterface2<T>
{
    void Interface2Method();
}
answered on Stack Overflow Sep 15, 2020 by Andy Vaal

User contributions licensed under CC BY-SA 3.0