在Microsoft依赖注入中获得开放式通用服务

时间:2018-11-27 21:23:36

标签: c# generics asp.net-core dependency-injection

假设我们提供以下服务:

interface IService { }
interface IService<T> : IService {
    T Get();
}

在ASP.Net-Core中,我们用不同的T注册了一些实现后,我们可以像这样获得所有注册的服务:

IEnumerable<IService> services = serviceProvider.GetServices<IService>();

现在,因为我需要从另一个不是选项的接口访问通用类型参数。 如何在不丢失通用类型的情况下检索IService<T>的所有实现?类似于:

IEnumerable<IService<T>> services = serviceProvider.GetServices<IService<T>>();
foreach (var s in services) {
    Method(s);
}

// Here we have a generic method I don't have control over.
// I want to call the method for each `T` registered in DI
void Method<T>(IService<T> service) {
    Type t = typeof(T); // This here will resolve to the actual type, different in each call. Not object or whatever less derived.
}

所有这些都应该具有不错的性能。

2 个答案:

答案 0 :(得分:1)

紧跟@JeroenMostert的有用评论之后,我发现了一种完全按照自己的意愿做事的方法。正如他指出的那样,由于我们在编译时不知道泛型参数类型,因此无法静态绑定该方法调用。我们需要的是late binding

反射是一种后期绑定,但是有一个更好的解决方案:dynamic 答案中的示例将变为:

IEnumerable<IService> services = serviceProvider.GetServices<IService>();
foreach (var s in services) {
    Method((dynamic)s);
}

void Method<T>(IService<T> service) {
    // This here will resolve to the actual type, different in each call. Not object or whatever less derived.
    Type t = typeof(T);
}

当知道dynamic的实际类型时,对s的强制转换将推迟方法绑定,直到运行时为止。然后它将寻找最合适的过载(如果没有异常,将引发异常)。这种方法与使用反射相比有一些优势:

  • 我们不必关心缓存。 DLR(动态语言运行时)将为我们处理它。
  • 我们绑定得晚,但是要获得尽可能多的静态分析。例如。所有其他参数类型都将被检查,并且在无效时可能导致编译时错误。
  • 它更短,更容易编写。

您可以阅读有关该方法及其与反射here的比较的出色的深入文章。

答案 1 :(得分:0)

我可以想到2种选择:

  1. 注入IService并过滤掉不兼容的类型:

    serviceProvider.GetServices<IService>().OfType<IService<T>>();
    
  2. 进行重复注册:

    services.AddScoped<IService, FooService>();
    services.AddScoped<IService, BarService1>();
    services.AddScoped<IService, BarService2>();
    services.AddScoped<IService<Foo>, FooService>();
    services.AddScoped<IService<Bar>, BarService1>();
    services.AddScoped<IService<Bar>, BarService2>();
    
    serviceProvider.GetServices<IService<Bar>>(); // returns 2 services
    serviceProvider.GetServices<IService>(); // returns 3 services
    

    但是请注意,您需要小心使用这些重复的注册,以免落入Torn Lifestyles陷阱中。将服务注册为ScopedSingleton时可能会发生这种情况。为了解决这个问题,您需要将以上注册更改为以下内容:

    services.AddScoped<FooService>();
    services.AddScoped<BarService1>();
    services.AddScoped<BarService2>();
    
    services.AddScoped<IService>(c => c.GetRequiredService<FooService>());
    services.AddScoped<IService>(c => c.GetRequiredService<BarService1>());
    services.AddScoped<IService>(c => c.GetRequiredService<BarService2>());
    services.AddScoped<IService<Foo>>(c => c.GetRequiredService<FooService>());
    services.AddScoped<IService<Bar>>(c => c.GetRequiredService<BarService1>());
    services.AddScoped<IService<Bar>>(c => c.GetRequiredService<BarService2>());
    

另外,由于您似乎在盖子下使用了其他容器,因此您可以使用自动注册(也称为程序集扫描)来减少样板。