假设我们有以下服务:
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.
}
所有这一切都应该有一些不错的表现。
根据@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的比较。
我能想到两个选项:
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>());
此外,由于您似乎在封面下使用不同的容器,您可以使用自动注册(a.k.a。组件扫描)来减少样板。