You can see the performance comparison here:
http://www.palmmedia.de/blog/2011/8/30/ioc-container-benchmark-performance-comparison
It is however not very extensible. It's pretty easy to switch to something else, but if you really want to stay with the built in IOC you have to invest some time into extending it. The two classes which are the cornerstone of this IOC ServiceProvider and ServiceCollection. One of the issues I've had with it, was implementation of a decorator pattern.
http://www.dofactory.com/net/decorator-design-pattern
The following implementation, which I've had created is small and lightwaight. But it is also quite slow. I've omitted the null checks and most of the validation to make it more concise. At the end I'll note what could be done to make it faster and potentially more robust.
servicesCollection.AddTransient<IDataSource, DataSource>(); servicesCollection.AddDecorator<IDataSource, DataSource2>();
AddTransient is just an ordinary type registration method of the .net core IOC. AddDecorator looks as follows:
public static void AddDecorator<TService, TDecorator>(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Scoped) where TService : class where TDecorator : class { var serviceDescriptor = new ServiceDescriptor( typeof(TService), provider => Construct<TService, TDecorator>(provider, services), lifetime); services.Add(serviceDescriptor); }
One of the ways of extending how the types are resolved is by adding a factory based registration as above. All the container's methods are based on service descriptors, and we are using on the available ones. Unfortunately, factory registrations are not generic, and hence they don't store the resolved type in the service collection - it would be much easier if that was not the case.
private static TDecorator Construct<TService, TDecorator>(IServiceProvider serviceProvider, IServiceCollection services) where TDecorator : class where TService : class { var type = GetDecoratedType<TService>(services); var decoratedConstructor = GetConstructor(type); var decoratorConstructor = GetConstructor(typeof(TDecorator)); var docoratedDependencies = serviceProvider.ResolveConstructorDependencies( decoratedConstructor.GetParameters()); var decoratedService = decoratedConstructor.Invoke(docoratedDependencies.ToArray()) as TService; var decoratorDependencies = serviceProvider.ResolveConstructorDependencies( decoratedService, decoratorConstructor.GetParameters()); return decoratorConstructor.Invoke(decoratorDependencies.ToArray()) as TDecorator; }
The factory method takes in the generic type TDecorator of the decorator service and the TService type of the interface it implements.
First, we need to find the decorated type, which is already registered in the container's service collection.
private static Type GetDecoratedType<TService>(IServiceCollection services) { if (services.Count(p => p.ServiceType == typeof(TService) && p.ImplementationFactory == null) > 1) { throw new InvalidOperationException( $"Only one decorated service for interface {nameof(TService)} allowed"); } var nonFactoryDescriptor = services.FirstOrDefault(p => p.ServiceType == typeof(TService) && p.ImplementationFactory == null); return nonFactoryDescriptor?.ImplementationType; }
To find the correct implementation we need to find the one which implement the interface TService and which doesn't have a factory based registration (because only the decorator class can have such).
Then we return the type.
private static ConstructorInfo GetConstructor(Type type) { var availableConstructors = type .GetConstructors() .Where(c => c.IsPublic) .ToList(); if (availableConstructors.Count!= 1) { throw new InvalidOperationException("Only single constructor types are supported"); } return availableConstructors.First(); }
Then we create constructors for both the decorator and the decorated services. We only support single public constructor classes. With the constructor info we have access to the number and types of all constructor parameters for these 2 classes. We resolve these dependencies using the ServiceProvider. In decorator's case we inject the constructed decorated service's instance. Here is the example of the extension methods for the ServiceProvider.
public static List<object> ResolveConstructorDependencies<TService>( this IServiceProvider serviceProvider, TService decorated, IEnumerable<ParameterInfo> constructorParameters) { var depencenciesList = new List<object>(); foreach (var parameter in constructorParameters) { if (parameter.ParameterType == typeof(TService)) { depencenciesList.Add(decorated); } else { var resolvedDependency = serviceProvider.GetService(parameter.ParameterType); depencenciesList.Add(resolvedDependency); } } return depencenciesList; } public static List<object> ResolveConstructorDependencies( this IServiceProvider serviceProvider, IEnumerable<ParameterInfo> constructorParameters) { var depencenciesList = new List<object>(); foreach (var parameter in constructorParameters) { var resolvedDependency = serviceProvider.GetService(parameter.ParameterType); depencenciesList.Add(resolvedDependency); } return depencenciesList; }
Having materialized a collection of dependencies, we can create an instance of the Decorator class with previously injected Decorated instance, using the Invoke method of the constructor.
decoratorConstructor.Invoke(decoratorDependencies.ToArray());
The exact sources are available in the following github repository:
https://github.com/simonkatanski/dependencyinjection.extensions
The things to improve:
- null checks
- more validation
- constructor lookup (instead of creating constructor each time we could just add it to some sort of dictionary lookup during registration and then just reuse it on each call
- support for multiple constructors (this container has it by default, if you venture into its code with reflection)
- implement registrations out of order
- find a way to have factory based decorator registration
No comments:
Post a Comment