Registro de serviço que implementa várias interfaces como singleton usando tipo genérico aberto

Sep 15 2020

Existe um genérico MyClass<T>que implementa 2 interfaces genéricas IMyInterface1<T>e IMyInterface2<T>. Eu quero registrá-lo como um singleton. Portanto, para um determinado, Tambas as interfaces serão resolvidas para a mesma instância de MyClass.

Estou tentando registrá-lo como um tipo genérico aberto com encaminhamento para um método de fábrica:

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

mas, em tempo de execução, estou recebendo uma exceção com a mensagem " Abrir tipo de serviço genérico 'IMyInterface1`1 [T]' requer o registro de um tipo de implementação genérico aberto. "

Quando estou registrando meu singleton para cada tipo de concreto T, ou seja, sem tipo genérico aberto, tudo funciona bem:

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>)));

Eu sei que há outra maneira de registrar um serviço singleton com duas interfaces - fornecendo instância de implementação em vez de fábrica de implementação. Mas não posso usar isso no "mundo real" porque minha classe real também tem muitas dependências e provavelmente é impossível resolver todas elas no estágio de registro.

Existe uma maneira de resolver esse problema usando tipos genéricos abertos?


Exceção detalhada:

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

Respostas

2 AndyVaal Sep 15 2020 at 16:17

Parece que você não pode fazer isso - não é compatível.

Os seguintes problemas parecem já ter solicitado isso:

  • implementaçãoFactory para genéricos abertos
  • Injeção de dependência de genéricos abertos via fábrica

Se você substituir sp => sp.GetRequiredService(typeof(MyClass<A>))nos registros de interface por typeof(MyClass<A>), você terminará com uma instância singleton para cada interface que você registrou, que não soa como você deseja.

O melhor que consigo pensar, e não é particularmente bonito, é que você se transforma MyClass<T>em uma fachada que gerencia sua própria instância singleton da implementação subjacente. Você poderia fazer isso fazendo com que a fachada chame uma classe não genérica com um dicionário estático (membros estáticos são por tipo genérico, portanto, precisa ser uma classe não genérica) para recuperar uma instância verdadeiramente única por T.

Exemplo

Registre seus serviços:

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

Implementação de fachada:

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();
}