假设我们提供以下服务:
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.
}
所有这些都应该具有不错的性能。
答案 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
的强制转换将推迟方法绑定,直到运行时为止。然后它将寻找最合适的过载(如果没有异常,将引发异常)。这种方法与使用反射相比有一些优势:
您可以阅读有关该方法及其与反射here的比较的出色的深入文章。
答案 1 :(得分:0)
我可以想到2种选择:
注入IService
并过滤掉不兼容的类型:
serviceProvider.GetServices<IService>().OfType<IService<T>>();
进行重复注册:
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陷阱中。将服务注册为Scoped
或Singleton
时可能会发生这种情况。为了解决这个问题,您需要将以上注册更改为以下内容:
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>());
另外,由于您似乎在盖子下使用了其他容器,因此您可以使用自动注册(也称为程序集扫描)来减少样板。