Reputation: 8541
Suppose we have the following services:
interface IService { }
interface IService<T> : IService {
T Get();
}
In ASP.Net-Core, after we have registered some implementations with different T
we can get all registered services like this:
IEnumerable<IService> services = serviceProvider.GetServices<IService>();
Now, because I need access to the generic type parameter from the other interface that is not an option. How can I retrieve all implementations of IService<T>
without losing the generic type? Something like:
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.
}
And all of this should have a somewhat decent performance.
Upvotes: 5
Views: 5486
Reputation: 8541
Following up on the helpful comments from @JeroenMostert I discovered a way to do exactly what I want. As he pointed out since we don't know the generic parameter type at compile time, we can't statically bind that method call. What we need is late binding.
Reflection is a type of late binding, but there is a better solution to it: dynamic
The example from the answer would become:
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);
}
The cast to dynamic
will postpone method binding until runtime when the actual type of s
is known. Then it will look for the best fitting overload (if there is none an exception would be thrown). This approach has some advantages to using reflection:
You can read an excellent in-depth post about this approach and how it compares to reflection here.
Upvotes: 6
Reputation: 172646
There are 2 options that I can think of:
Inject an IService
and filter out the incompatible types:
serviceProvider.GetServices<IService>().OfType<IService<T>>();
Make duplicate registrations:
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
Do note, however, that you need to be careful with these duplicate registrations to not fall into the Torn Lifestyles trap. This can happen when a service is registered as Scoped
or Singleton
. To combat this, you need to change the above registrations to the following:
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>());
Additionally, as you seem to be using a different container under the covers, you might be able to reduce the boilerplate using Auto-Registration (a.k.a. assembly scanning).
Upvotes: 3