-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
Often in ASP.NET Core application type is being repeatedly resolved at runtime but is not known at compile time and couldn't be made a true constructor injected dependency.
Some examples:
- Middleware activation
- Everything that uses ActivatorUtilities
All these usages are forced to call GetService<>() that incurs a resolver lookup cost for every call that might be proportionally very large in case of simple service type/singletons and in general very efficient resolvers.
Proposal
Add 2 types:
public interface IServiceActivator
{
object GetService(IServiceProvider provider);
}
public interface IServiceActivatorFactory
{
IServiceActivator Create(Type serviceType);
}IServiceActivatorFactory should be automatically registered in IServiceProvider implementations (same as IServiceScopeProvider).
IServiceActivatorFactory.Create would return an IServerActivator instance that activates services using provided IServerProvider (IServiceProvider has to be passed in to support resolution from IServiceScope's).
IServiceActivatorFactory.Create would return null if service is not registered.
IServiceActivatorFactory.Create is free to throw when service is registered but it's construction is impossible (dependencies not satisfied, etc.)
IServiceProvider passed into IServiceActivator.GetService is expected to be the same IServiceActivatorFactory was resolved from or child scope thereof. Implementation is free to validate this assumption but said validation is not required.
IServiceActivatorFactory.Create should support resolution all the types GetService does.
3rd party containers and adoption planning
This feature would become a part of spec tests but all usages in ASP.NET Core 3.x would be resilient to IServiceActivatorFactory not being available in container (it's very easy to fallback to () => GetService(type)).
It is very easy for container implementations to add a trivial implemenations too.
Benchmarks
Quick implementation in MEDI showed 2-3x performance improvement in ActivatorUtilities performance
Method | UseActivatorFactory | Mean | Error | StdDev | Op/s | Gen 0 | Allocated |
------------------------ |-------------------- |----------:|-----------:|-----------:|-------------:|-------:|----------:|
ServiceProvider | False | 36.74 ns | 0.3555 ns | 0.3325 ns | 27,219,949.5 | 0.0001 | 24 B |
Factory | False | 44.27 ns | 0.8717 ns | 0.8154 ns | 22,586,712.3 | 0.0001 | 24 B |
Factory3ArgumentsFromDI | False | 106.25 ns | 1.3168 ns | 1.0996 ns | 9,411,498.7 | 0.0001 | 24 B |
CreateInstance | False | 839.81 ns | 15.6118 ns | 15.3328 ns | 1,190,745.0 | 0.0010 | 328 B |
ServiceProvider | True | 38.78 ns | 0.2703 ns | 0.2529 ns | 25,787,513.2 | 0.0001 | 24 B |
Factory | True | 17.44 ns | 0.1417 ns | 0.1256 ns | 57,335,732.4 | 0.0001 | 24 B |
Factory3ArgumentsFromDI | True | 37.36 ns | 0.3099 ns | 0.2747 ns | 26,767,283.6 | 0.0001 | 24 B |
CreateInstance | True | 877.28 ns | 17.8947 ns | 24.4944 ns | 1,139,887.2 | 0.0010 | 328 B |
Prototype is in https://github.com/aspnet/Extensions/tree/pakrym/IServiceActivator