// Author: simon.gockner // Created: 2019-05-20 // Copyright(c) 2019 SimonG. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using JetBrains.Annotations; using LightweightIocContainer.Exceptions; using LightweightIocContainer.Interfaces; using LightweightIocContainer.Interfaces.Factories; using LightweightIocContainer.Interfaces.Installers; using LightweightIocContainer.Interfaces.Registrations; using LightweightIocContainer.Interfaces.Registrations.Fluent; using LightweightIocContainer.Registrations; namespace LightweightIocContainer { /// /// The main container that carries all the s and can resolve all the types you'll ever want /// public class IocContainer : IIocContainer { private readonly RegistrationFactory _registrationFactory; private readonly List<(Type type, object instance)> _singletons = new(); private readonly List<(Type type, Type scope, ConditionalWeakTable instances)> _multitons = new(); /// /// The main container that carries all the s and can resolve all the types you'll ever want /// public IocContainer() { _registrationFactory = new RegistrationFactory(this); Registrations = new List(); } internal List Registrations { get; } /// /// Install the given installers for the current /// /// The given s /// An instance of the current public IIocContainer Install(params IIocInstaller[] installers) { foreach (IIocInstaller installer in installers) installer.Install(this); return this; } /// /// Register an Interface with a Type that implements it /// /// The Interface to register /// The Type that implements the interface /// The for this /// The created public ITypedRegistration Register(Lifestyle lifestyle = Lifestyle.Transient) where TImplementation : TInterface { ITypedRegistration registration = _registrationFactory.Register(lifestyle); Register(registration); return registration; } /// /// Register an open generic Interface with an open generic Type that implements it /// /// The open generic Interface to register /// The open generic Type that implements the interface /// The for this /// The created /// Function can only be used to register open generic types /// Can't register a multiton with open generic registration public IOpenGenericRegistration RegisterOpenGenerics(Type tInterface, Type tImplementation, Lifestyle lifestyle = Lifestyle.Transient) { if (!tInterface.ContainsGenericParameters) throw new InvalidRegistrationException("This function can only be used to register open generic types."); if (lifestyle == Lifestyle.Multiton) throw new InvalidRegistrationException("Can't register a multiton with open generic registration."); //TODO: Is there any need for a possibility to register multitons with open generics? IOpenGenericRegistration registration = _registrationFactory.Register(tInterface, tImplementation, lifestyle); Register(registration); return registration; } /// /// Register multiple interfaces for a that implements them /// /// The base interface to register /// A second interface to register /// The that implements both interfaces /// The for this /// The created public IMultipleRegistration Register(Lifestyle lifestyle = Lifestyle.Transient) where TImplementation : TInterface2, TInterface1 { IMultipleRegistration multipleRegistration = _registrationFactory.Register(lifestyle); Register(multipleRegistration); return multipleRegistration; } /// /// Register multiple interfaces for a that implements them /// /// The base interface to register /// A second interface to register /// A third interface to register /// The that implements both interfaces /// The for this /// The created public IMultipleRegistration Register(Lifestyle lifestyle = Lifestyle.Transient) where TImplementation : TInterface3, TInterface2, TInterface1 { IMultipleRegistration multipleRegistration = _registrationFactory.Register(lifestyle); Register(multipleRegistration); return multipleRegistration; } /// /// Register multiple interfaces for a that implements them /// /// The base interface to register /// A second interface to register /// A third interface to register /// A fourth interface to register /// The that implements both interfaces /// The for this /// The created public IMultipleRegistration Register(Lifestyle lifestyle = Lifestyle.Transient) where TImplementation : TInterface4, TInterface3, TInterface2, TInterface1 { IMultipleRegistration multipleRegistration = _registrationFactory.Register(lifestyle); Register(multipleRegistration); return multipleRegistration; } /// /// Register multiple interfaces for a that implements them /// /// The base interface to register /// A second interface to register /// A third interface to register /// A fourth interface to register /// A fifth interface to register /// The that implements both interfaces /// The for this /// The created public IMultipleRegistration Register(Lifestyle lifestyle = Lifestyle.Transient) where TImplementation : TInterface5, TInterface4, TInterface3, TInterface2, TInterface1 { IMultipleRegistration multipleRegistration = _registrationFactory.Register(lifestyle); Register(multipleRegistration); return multipleRegistration; } /// /// Register a without an interface /// /// The to register /// The for this /// The created public ISingleTypeRegistration Register(Lifestyle lifestyle = Lifestyle.Transient) { ISingleTypeRegistration registration = _registrationFactory.Register(lifestyle); Register(registration); return registration; } /// /// Register an Interface with a Type that implements it as a multiton /// /// The Interface to register /// The Type that implements the interface /// The Type of the multiton scope /// The created public IMultitonRegistration RegisterMultiton() where TImplementation : TInterface { IMultitonRegistration registration = _registrationFactory.RegisterMultiton(); Register(registration); return registration; } /// /// Register multiple interfaces for a that implements them as a multiton /// /// The base interface to register /// A second interface to register /// The Type that implements the interface /// The Type of the multiton scope /// The created public IMultipleMultitonRegistration RegisterMultiton() where TImplementation : TInterface1, TInterface2 { IMultipleMultitonRegistration registration = _registrationFactory.RegisterMultiton(); Register(registration); return registration; } /// /// Register an Interface as an abstract typed factory /// /// The abstract typed factory to register /// The created internal void RegisterFactory(ITypedFactory factory) => Register(_registrationFactory.RegisterFactory(factory)); /// /// Add the to the the /// /// The given /// The is already registered in this private void Register(IRegistration registration) { //if type is already registered if (Registrations.Any(r => r.InterfaceType == registration.InterfaceType)) throw new MultipleRegistrationException(registration.InterfaceType); //don't allow lifestyle.multiton without iMultitonRegistration if (registration is ILifestyleProvider lifestyleProvider && lifestyleProvider.Lifestyle == Lifestyle.Multiton && !(registration is IMultitonRegistration)) throw new InvalidRegistrationException("Can't register a type as Lifestyle.Multiton without a scope (Registration is not of type IMultitonRegistration)."); Registrations.Add(registration); } /// /// Register all from an /// /// The of the first registered interface /// The of the registered implementation /// The private void Register(IMultipleRegistration multipleRegistration) where TImplementation : TInterface1 { foreach (IRegistration registration in multipleRegistration.Registrations) Register(registration); } /// /// Gets an instance of the given /// /// The given /// An instance of the given public virtual T Resolve() => ResolveInternal(null); /// /// Gets an instance of the given /// /// The given /// The constructor arguments /// An instance of the given public T Resolve(params object[] arguments) => ResolveInternal(arguments); /// /// Gets an instance of the given /// /// The given /// The constructor arguments /// The current resolve stack /// An instance of the given /// Could not find function internal object Resolve(Type type, object[] arguments, List resolveStack) => GenericMethodCaller.Call(this, nameof(ResolveInternal), type, BindingFlags.NonPublic | BindingFlags.Instance, arguments, resolveStack); /// /// Gets an instance of a given registered /// /// The registered /// The constructor arguments /// The current resolve stack /// An instance of the given registered /// The given is not registered in this /// The registration for the given has an unknown private T ResolveInternal(object[] arguments, List resolveStack = null) { IRegistration registration = FindRegistration(); if (registration == null) throw new TypeNotRegisteredException(typeof(T)); //Circular dependency check if (resolveStack == null) //first resolve call resolveStack = new List {typeof(T)}; //create new stack and add the currently resolving type to the stack else if (resolveStack.Contains(typeof(T))) throw new CircularDependencyException(typeof(T), resolveStack); //currently resolving type is still resolving -> circular dependency else //not the first resolve call in chain but no circular dependencies for now resolveStack.Add(typeof(T)); //add currently resolving type to the stack T resolvedInstance = registration switch { IRegistrationBase { Lifestyle: Lifestyle.Singleton } defaultRegistration => GetOrCreateSingletonInstance(defaultRegistration, arguments, resolveStack), IRegistrationBase { Lifestyle: Lifestyle.Multiton } and IMultitonRegistration multitonRegistration => GetOrCreateMultitonInstance(multitonRegistration, arguments, resolveStack), IRegistrationBase defaultRegistration => CreateInstance(defaultRegistration, arguments, resolveStack), ITypedFactoryRegistration typedFactoryRegistration => typedFactoryRegistration.Factory.Factory, _ => throw new UnknownRegistrationException($"There is no registration of type {registration.GetType().Name}.") }; resolveStack.Remove(typeof(T)); //T was successfully resolved -> no circular dependency -> remove from resolve stack return resolvedInstance; } /// /// Gets or creates a singleton instance of a given /// /// The given /// The registration of the given /// The arguments to resolve /// The current resolve stack /// An existing or newly created singleton instance of the given private T GetOrCreateSingletonInstance(IRegistration registration, object[] arguments, List resolveStack) { Type type = registration switch { ITypedRegistration typedRegistration => typedRegistration.ImplementationType, ISingleTypeRegistration singleTypeRegistration => singleTypeRegistration.InterfaceType, _ => throw new UnknownRegistrationException($"There is no registration {registration.GetType().Name} that can have lifestyle singleton.") }; //if a singleton instance exists return it object instance = _singletons.FirstOrDefault(s => s.type == type).instance; if (instance != null) return (T) instance; //if it doesn't already exist create a new instance and add it to the list T newInstance = CreateInstance(registration, arguments, resolveStack); _singletons.Add((type, newInstance)); return newInstance; } /// /// Gets or creates a multiton instance of a given /// /// The given /// The registration of the given /// The arguments to resolve /// The current resolve stack /// An existing or newly created multiton instance of the given /// No arguments given /// Scope argument not given private T GetOrCreateMultitonInstance(IMultitonRegistration registration, object[] arguments, List resolveStack) { if (arguments == null || !arguments.Any()) throw new MultitonResolveException("Can not resolve multiton without arguments.", typeof(T)); object scopeArgument = arguments[0]; if (scopeArgument.GetType() != registration.Scope && !registration.Scope.IsInstanceOfType(scopeArgument)) throw new MultitonResolveException($"Can not resolve multiton without the first argument being the scope (should be of type {registration.Scope}).", typeof(T)); //if a multiton for the given scope exists return it var instances = _multitons.FirstOrDefault(m => m.type == registration.ImplementationType && m.scope == registration.Scope).instances; //get instances for the given type and scope (use implementation type to resolve the correct instance for multiple multiton registrations as well) if (instances != null) { if (instances.TryGetValue(scopeArgument, out object instance)) return (T) instance; T createdInstance = CreateInstance(registration, arguments, resolveStack); instances.Add(scopeArgument, createdInstance); return createdInstance; } T newInstance = CreateInstance(registration, arguments, resolveStack); ConditionalWeakTable weakTable = new(); weakTable.Add(scopeArgument, newInstance); _multitons.Add((registration.ImplementationType, registration.Scope, weakTable)); return newInstance; } /// /// Creates an instance of a given /// /// The given /// The registration of the given /// The constructor arguments /// The current resolve stack /// A newly created instance of the given private T CreateInstance(IRegistration registration, object[] arguments, List resolveStack) { if (registration is IWithParameters { Parameters: { } } registrationWithParameters) arguments = UpdateArgumentsWithRegistrationParameters(registrationWithParameters, arguments); T instance; if (registration is IOpenGenericRegistration openGenericRegistration) { arguments = ResolveConstructorArguments(openGenericRegistration.ImplementationType, arguments, resolveStack); //create generic implementation type from generic arguments of T Type genericImplementationType = openGenericRegistration.ImplementationType.MakeGenericType(typeof(T).GenericTypeArguments); instance = (T) Activator.CreateInstance(genericImplementationType, arguments); } else if (registration is ITypedRegistration defaultRegistration) { arguments = ResolveConstructorArguments(defaultRegistration.ImplementationType, arguments, resolveStack); instance = (T) Activator.CreateInstance(defaultRegistration.ImplementationType, arguments); } else if (registration is ISingleTypeRegistration singleTypeRegistration) { if (singleTypeRegistration.InterfaceType.IsInterface && singleTypeRegistration.FactoryMethod == null) throw new InvalidRegistrationException($"Can't register an interface without its implementation type or without a factory method (Type: {singleTypeRegistration.InterfaceType})."); if (singleTypeRegistration.FactoryMethod == null) //type registration without interface -> just create this type { arguments = ResolveConstructorArguments(singleTypeRegistration.InterfaceType, arguments, resolveStack); instance = (T)Activator.CreateInstance(singleTypeRegistration.InterfaceType, arguments); } else //factory method set to create the instance instance = singleTypeRegistration.FactoryMethod(this); } else throw new UnknownRegistrationException($"There is no registration of type {registration.GetType().Name}."); if (registration is IOnCreate onCreateRegistration) onCreateRegistration.OnCreateAction?.Invoke(instance); //TODO: Allow async OnCreateAction? return instance; } /// /// Update the given arguments with the of the given /// /// The of the given /// The constructor arguments /// The argument list updated with the private object[] UpdateArgumentsWithRegistrationParameters(IWithParameters registration, object[] arguments) { if (arguments != null && arguments.Any()) //if more arguments were passed to resolve { int argumentsSize = registration.Parameters.Length + arguments.Length; object[] newArguments = new object[argumentsSize]; for (int i = 0; i < argumentsSize; i++) { if (i < registration.Parameters.Length) //if `i` is bigger than the length of the parameters, take the given arguments { object currentParameter = registration.Parameters[i]; if (currentParameter is not InternalResolvePlaceholder) //use the parameter at the current index if it is not a placeholder { newArguments[i] = currentParameter; continue; } } object firstArgument = arguments.FirstOrGiven(a => !(a is InternalResolvePlaceholder)); //find the first argument that is not a placeholder if (firstArgument is InternalResolvePlaceholder) //no more arguments available break; //there won't be any more arguments newArguments[i] = firstArgument; int indexOfFirstArgument = Array.IndexOf(arguments, firstArgument); arguments[indexOfFirstArgument] = new InternalResolvePlaceholder(); } arguments = newArguments; } else //no more arguments were passed to resolve -> only use parameters set during registration arguments = registration.Parameters; return arguments; } /// /// Resolve the missing constructor arguments /// /// The that will be created /// The existing arguments /// The current resolve stack /// An array of all needed constructor arguments to create the /// No matching constructor was found for the given or resolvable arguments [CanBeNull] private object[] ResolveConstructorArguments(Type type, object[] arguments, List resolveStack) { //find best ctor List sortedConstructors = type.GetConstructors().OrderByDescending(c => c.GetParameters().Length).ToList(); if (!sortedConstructors.Any()) //no public constructor available throw new NoPublicConstructorFoundException(type); NoMatchingConstructorFoundException noMatchingConstructorFoundException = null; foreach (ConstructorInfo ctor in sortedConstructors) { try { List argumentsList = arguments?.ToList(); List ctorParams = new(); ParameterInfo[] parameters = ctor.GetParameters(); foreach (ParameterInfo parameter in parameters) { object fittingArgument = new InternalResolvePlaceholder(); if (argumentsList != null) { fittingArgument = argumentsList.FirstOrGiven(a => a?.GetType() == parameter.ParameterType || parameter.ParameterType.IsInstanceOfType(a)); if (fittingArgument is not InternalResolvePlaceholder) { int index = argumentsList.IndexOf(fittingArgument); argumentsList[index] = new InternalResolvePlaceholder(); } else //fittingArgument is InternalResolvePlaceholder { try { fittingArgument = Resolve(parameter.ParameterType, null, resolveStack); } catch (Exception) { fittingArgument = argumentsList.FirstOrGiven(a => parameter.ParameterType.GetDefault() == a); if (fittingArgument is not InternalResolvePlaceholder) { int index = argumentsList.IndexOf(fittingArgument); argumentsList[index] = new InternalResolvePlaceholder(); } } } } if (fittingArgument is InternalResolvePlaceholder && parameter.HasDefaultValue) ctorParams.Add(parameter.DefaultValue); else if (fittingArgument is InternalResolvePlaceholder) ctorParams.Add(Resolve(parameter.ParameterType, null, resolveStack)); else ctorParams.Add(fittingArgument); } return ctorParams.ToArray(); } catch (CircularDependencyException) //don't handle circular dependencies as no matching constructor, just rethrow them { throw; } catch (Exception ex) { noMatchingConstructorFoundException ??= new NoMatchingConstructorFoundException(type); noMatchingConstructorFoundException.AddInnerException(new ConstructorNotMatchingException(ctor, ex)); } } if (noMatchingConstructorFoundException != null) throw noMatchingConstructorFoundException; return null; } [CanBeNull] private IRegistration FindRegistration() { IRegistration registration = Registrations.FirstOrDefault(r => r.InterfaceType == typeof(T)); if (registration != null) return registration; registration = Registrations.OfType().FirstOrDefault(r => r.ImplementationType == typeof(T)); if (registration != null) return registration; //check for open generic registration if (!typeof(T).GenericTypeArguments.Any()) return null; List openGenericRegistrations = Registrations.Where(r => r.InterfaceType.ContainsGenericParameters).ToList(); return !openGenericRegistrations.Any() ? null : openGenericRegistrations.FirstOrDefault(r => r.InterfaceType == typeof(T).GetGenericTypeDefinition()); } /// /// Clear the multiton instances of the given from the registered multitons list /// /// The to clear the multiton instances public void ClearMultitonInstances() { IRegistration registration = FindRegistration(); if (!(registration is IMultitonRegistration multitonRegistration)) return; var multitonInstance = _multitons.FirstOrDefault(m => m.type == multitonRegistration.ImplementationType); //it is allowed to clear a non existing multiton instance (don't throw an exception) if (multitonInstance == default) return; _multitons.Remove(multitonInstance); } /// /// Is the given registered with this /// /// The given /// True if the given is registered with this , false if not public bool IsTypeRegistered() => FindRegistration() != null; /// /// The method /// public void Dispose() { Registrations.Clear(); _singletons.Clear(); _multitons.Clear(); } } }