// Author: simon.gockner // Created: 2019-05-20 // Copyright(c) 2019 SimonG. All Rights Reserved. using System.Reflection; using System.Runtime.CompilerServices; 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; using LightweightIocContainer.ResolvePlaceholders; namespace LightweightIocContainer; /// /// The main container that carries all the s and can resolve all the types you'll ever want /// public class IocContainer : IIocContainer, IIocResolver { private readonly RegistrationFactory _registrationFactory; private readonly List<(Type type, object? instance)> _singletons = []; private readonly List<(Type type, Type scope, ConditionalWeakTable instances)> _multitons = []; private readonly List _ignoreConstructorAttributes; /// /// The main container that carries all the s and can resolve all the types you'll ever want /// public IocContainer() { _registrationFactory = new RegistrationFactory(this); _ignoreConstructorAttributes = []; Registrations = []; } 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) { RegistrationCollector registrationCollector = new(_registrationFactory); installer.Install(registrationCollector); registrationCollector.Registrations.ForEach(Register); } return this; } /// /// Register an at this /// /// The that creates an public void Register(Func addRegistration) { RegistrationCollector registrationCollector = new(_registrationFactory); addRegistration(registrationCollector); registrationCollector.Registrations.ForEach(Register); } /// /// Register an Interface as an abstract typed factory /// /// The abstract typed factory to register /// The created internal void RegisterFactory(ITypedFactory factory) { ITypedFactoryRegistration typedFactoryRegistration = _registrationFactory.RegisterFactory(factory); if (!Registrations.Contains(typedFactoryRegistration)) Registrations.Add(typedFactoryRegistration); } /// /// Add the to the the /// /// The given /// The is already registered in this private void Register(IRegistration registration) { IRegistration? sameTypeRegistration = Registrations.FirstOrDefault(r => r.InterfaceType == registration.InterfaceType); if (sameTypeRegistration != null && !registration.Equals(sameTypeRegistration)) //if type is already registered differently throw new MultipleRegistrationException(registration.InterfaceType); if (registration is IInternalValidationProvider validationProvider) validationProvider.Validate(); Registrations.Add(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 for a factory /// /// The given /// The constructor arguments /// An instance of the given public T FactoryResolve(params object?[] arguments) => ResolveInternal(arguments, null, true); /// /// Gets an instance of a given registered /// /// The registered /// The constructor arguments /// The current resolve stack /// True if resolve is called from factory, false (default) if not /// An instance of the given registered private T ResolveInternal(object?[]? arguments, List? resolveStack = null, bool isFactoryResolve = false) { (bool success, object resolvedObject, Exception? exception) = TryResolve(arguments, resolveStack, isFactoryResolve); if (success) return ResolveInstance(resolvedObject); if (exception is not null) throw exception; throw new Exception("Resolve Error"); } /// /// Tries to resolve the given with the given arguments /// /// The given arguments /// The current resolve stack /// True if resolve is called from factory, false (default) if not /// The registered /// An instance of the given registered , an if parameters need to be resolved or an if a factory method is used to create an instance /// The given is not registered /// A direct resolve with a registered factory is not allowed /// An interface was registered without an implementation or factory method /// Tried resolving a multiton without scope argument /// No matching constructor for the given found /// Getting resolve stack failed without exception private (bool success, object resolvedObject, Exception? exception) TryResolve(object?[]? arguments, List? resolveStack, bool isFactoryResolve = false) { IRegistration? registration = FindRegistration(); if (registration == null) return (false, new object(), new TypeNotRegisteredException(typeof(T))); List internalResolveStack = resolveStack == null ? [] : [..resolveStack]; (bool success, internalResolveStack, CircularDependencyException? circularDependencyException) = CheckForCircularDependencies(internalResolveStack); if (!success && circularDependencyException is not null) return (false, new object(), circularDependencyException); if (!success) throw new Exception("Invalid return type!"); object? existingInstance = TryGetExistingInstance(registration, arguments); if (existingInstance != null) return (true, existingInstance, null); switch (registration) { case IWithFactoryInternal { Factory: not null } when !isFactoryResolve: return (false, new object(), new DirectResolveWithRegisteredFactoryNotAllowed(typeof(T))); case ISingleTypeRegistration { InterfaceType.IsInterface: true, FactoryMethod: null } singleTypeRegistration: return (false, new object(), new InvalidRegistrationException($"Can't register an interface without its implementation type or without a factory method (Type: {singleTypeRegistration.InterfaceType}).")); case ISingleTypeRegistration { FactoryMethod: not null } singleTypeRegistration: return (true, new InternalFactoryMethodPlaceholder(singleTypeRegistration), null); } if (registration is IWithParametersInternal { Parameters: not null } registrationWithParameters) arguments = UpdateArgumentsWithRegistrationParameters(registrationWithParameters, arguments); Type registeredType = GetType(registration); (bool result, List? parametersToResolve, NoMatchingConstructorFoundException? exception) = TryGetTypeResolveStack(registeredType, arguments, internalResolveStack); if (registration is IMultitonRegistration multitonRegistration) { if (arguments == null || !arguments.Any()) return (false, new object(), new MultitonResolveException("Can not resolve multiton without arguments.", registration.InterfaceType)); object multitonScopeArgument = TryGetMultitonScopeArgument(multitonRegistration, arguments); parametersToResolve ??= []; parametersToResolve.Insert(0, multitonScopeArgument); //insert scope at first place, won't be passed to ctor when creating multiton } if (result) return (true, new InternalToBeResolvedPlaceholder(registeredType, registration, parametersToResolve), null); if (exception != null) return (false, new object(), exception); return (false, new object(), new InternalResolveException("Getting resolve stack failed without exception.")); } /// /// Tries to resolve the given with the given arguments without generic arguments /// /// The registered /// The given arguments /// The current resolve stack /// True if resolve is called from factory, false (default) if not /// An instance of the given registered , an if parameters need to be resolved or an if a factory method is used to create an instance /// The given is not registered /// A direct resolve with a registered factory is not allowed /// An interface was registered without an implementation or factory method /// Tried resolving a multiton without scope argument /// No matching constructor for the given found /// Getting resolve stack failed without exception internal (bool success, object resolvedObject, Exception? exception) TryResolveNonGeneric(Type type, object?[]? arguments, List? resolveStack, bool isFactoryResolve = false) { MethodInfo? method = typeof(IocContainer).GetMethod(nameof(TryResolve), BindingFlags.NonPublic | BindingFlags.Instance); MethodInfo? genericMethod = method?.MakeGenericMethod(type); if (genericMethod == null) throw new GenericMethodNotFoundException(nameof(TryResolve)); object? resolvedValue = genericMethod.Invoke(this, [arguments, resolveStack, isFactoryResolve]); if (resolvedValue is not ValueTuple resolvedTuple) throw new Exception("Invalid return value!"); return resolvedTuple; } /// /// Recursively resolve a with the given parameters for an /// /// The that includes the type and resolve stack /// A recursively resolved instance of the given private T ResolvePlaceholder(InternalToBeResolvedPlaceholder toBeResolvedPlaceholder) { object? existingInstance = TryGetExistingInstance(toBeResolvedPlaceholder.ResolvedRegistration, toBeResolvedPlaceholder.Parameters); if (existingInstance is T instance) return instance; if (toBeResolvedPlaceholder.Parameters == null) return CreateInstance(toBeResolvedPlaceholder.ResolvedRegistration, null); List parameters = []; foreach (object? parameter in toBeResolvedPlaceholder.Parameters) { if (parameter != null) { Type type = parameter is IInternalToBeResolvedPlaceholder internalToBeResolvedPlaceholder ? internalToBeResolvedPlaceholder.ResolvedType : parameter.GetType(); parameters.Add(ResolveInstanceNonGeneric(type, parameter)); } else parameters.Add(parameter); } return CreateInstance(toBeResolvedPlaceholder.ResolvedRegistration, parameters.ToArray()); } /// /// Resolve the given object instance /// /// The given resolved object /// The of the returned instance /// An instance of the given resolved object /// Resolve returned wrong type private T ResolveInstance(object resolvedObject) => resolvedObject switch { T instance => instance, InternalToBeResolvedPlaceholder toBeResolvedPlaceholder => ResolvePlaceholder(toBeResolvedPlaceholder), InternalFactoryMethodPlaceholder factoryMethodPlaceholder => factoryMethodPlaceholder.FactoryMethod(this), _ => throw new InternalResolveException("Resolve returned wrong type.") }; /// /// Resolve the given object instance without generic arguments /// /// The of the returned instance /// The given resolved object /// An instance of the given resolved object /// Resolve returned wrong type private object? ResolveInstanceNonGeneric(Type type, object resolvedObject) => GenericMethodCaller.CallPrivate(this, nameof(ResolveInstance), type, resolvedObject); /// /// Creates an instance of a given /// /// The given /// The registration of the given /// The constructor arguments /// A newly created instance of the given private T CreateInstance(IRegistration registration, object?[]? arguments) { T instance; if (registration is IOpenGenericRegistration openGenericRegistration) { //create generic implementation type from generic arguments of T Type genericImplementationType = openGenericRegistration.ImplementationType.MakeGenericType(typeof(T).GenericTypeArguments); instance = Creator.CreateInstance(genericImplementationType, arguments); } else if (registration is ISingleTypeRegistration singleTypeRegistration) instance = singleTypeRegistration.FactoryMethod == null ? Creator.CreateInstance(singleTypeRegistration.InterfaceType, arguments) : singleTypeRegistration.FactoryMethod(this); else if (registration is ILifestyleProvider { Lifestyle: Lifestyle.Multiton } and IMultitonRegistration multitonRegistration) instance = CreateMultitonInstance(multitonRegistration, arguments); else if (registration is ITypedRegistration defaultRegistration) instance = Creator.CreateInstance(defaultRegistration.ImplementationType, arguments); else throw new UnknownRegistrationException($"There is no registration of type {registration.GetType().Name}."); if (registration is ILifestyleProvider { Lifestyle: Lifestyle.Singleton }) _singletons.Add((GetType(registration), instance)); if (registration is IOnCreate onCreateRegistration) onCreateRegistration.OnCreateAction?.Invoke(instance); //TODO: Allow async OnCreateAction? return instance; } /// /// Try to get an already existing instance (factory, singleton, multiton) /// /// The given /// The given arguments /// The of the instance /// An already existing instance if possible, null if not private object? TryGetExistingInstance(IRegistration registration, IReadOnlyList? arguments) => registration switch { ITypedFactoryRegistration typedFactoryRegistration => typedFactoryRegistration.Factory.Factory, ILifestyleProvider { Lifestyle: Lifestyle.Singleton } => TryGetSingletonInstance(registration), ILifestyleProvider { Lifestyle: Lifestyle.Multiton } and IMultitonRegistration multitonRegistration => TryGetMultitonInstance(multitonRegistration, arguments), _ => null }; /// /// Try to get an existing singleton instance for a given /// /// The /// A singleton instance if existing for the given , null if not private object? TryGetSingletonInstance(IRegistration registration) => _singletons.FirstOrDefault(s => s.type == GetType(registration)).instance; //if a singleton instance exists return it /// /// Try to get an existing multiton instance for a given /// /// The given /// The given arguments /// A multiton instance if existing for the given , null if not /// Tried resolving a multiton without scope argument private object? TryGetMultitonInstance(IMultitonRegistration registration, IReadOnlyList? arguments) { if (arguments == null || !arguments.Any()) throw new MultitonResolveException("Can not resolve multiton without arguments.", registration.InterfaceType); object scopeArgument = TryGetMultitonScopeArgument(registration, arguments); //if a multiton for the given scope exists return it var matchingMultitons = _multitons.FirstOrDefault(m => m.type == registration.ImplementationType && m.scope == registration.Scope); //get instances for the given type and scope (use implementation type to resolve the correct instance for multiple multiton registrations as well) if (matchingMultitons == default) return null; return matchingMultitons.instances.TryGetValue(scopeArgument, out object? instance) && instance != null ? instance : null; } /// /// Try to get the multiton scope argument for a given /// /// The given /// The given arguments /// The multiton scope argument for the given /// Tried resolving a multiton without correct scope argument private object TryGetMultitonScopeArgument(IMultitonRegistration registration, IReadOnlyList arguments) { 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}).", registration.InterfaceType); return scopeArgument; } /// /// Gets or creates a multiton instance of a given /// /// The given /// The registration of the given /// The arguments to resolve /// An existing or newly created multiton instance of the given /// No arguments given /// Scope argument not given private T CreateMultitonInstance(IMultitonRegistration registration, object?[]? arguments) { if (arguments == null || !arguments.Any()) throw new MultitonResolveException("Can not resolve multiton without arguments.", registration.InterfaceType); object scopeArgument = TryGetMultitonScopeArgument(registration, arguments); //if a multiton for the given scope exists return it var matchingMultitons = _multitons.FirstOrDefault(m => m.type == registration.ImplementationType && m.scope == registration.Scope); //get instances for the given type and scope (use implementation type to resolve the correct instance for multiple multiton registrations as well) if (matchingMultitons != default) { T createdInstance = Creator.CreateInstance(registration.ImplementationType, arguments[1..]); matchingMultitons.instances.Add(scopeArgument, createdInstance); return createdInstance; } T newInstance = Creator.CreateInstance(registration.ImplementationType, arguments[1..]); _multitons.Add((registration.ImplementationType, registration.Scope, new ConditionalWeakTable { { scopeArgument, newInstance } })); return newInstance; } /// /// 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(IWithParametersInternal registration, object?[]? arguments) { if (registration.Parameters == null) return arguments; if (arguments != null && arguments.Any()) //if more arguments were passed to resolve { int argumentsSize = registration.Parameters.Count(p => p is not InternalResolvePlaceholder) + 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 not 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; } /// /// Try to get the resolve stack for a given /// /// The given /// The given arguments /// The current resolve stack /// /// result: True if successful, false if not /// parameters: The parameters needed to resolve the given /// exception: A if no matching constructor was found /// private (bool result, List? parameters, NoMatchingConstructorFoundException? exception) TryGetTypeResolveStack(Type type, IReadOnlyCollection? arguments, List resolveStack) { NoMatchingConstructorFoundException? noMatchingConstructorFoundException = null; //find best ctor List sortedConstructors = TryGetSortedConstructors(type); foreach (ConstructorInfo constructor in sortedConstructors) { (bool result, List? parameters, List? exceptions) = TryGetConstructorResolveStack(type, constructor, arguments, resolveStack); if (result) return (true, parameters, null); noMatchingConstructorFoundException ??= new NoMatchingConstructorFoundException(type); exceptions?.ForEach(e => noMatchingConstructorFoundException.AddInnerException(e)); } return (false, null, noMatchingConstructorFoundException); } /// /// Try to get the resolve stack for a given constructor /// /// The that is currently getting resolved /// The for the given constructor /// The given arguments /// The current resolve stack /// /// result: True if successful, false if not /// parameters: The parameters needed to resolve the given /// exception: A List of s if the constructor is not matching /// private (bool result, List? parameters, List? exceptions) TryGetConstructorResolveStack(Type type, ConstructorInfo constructor, IReadOnlyCollection? arguments, List resolveStack) { List constructorParameters = constructor.GetParameters().ToList(); List exceptions = []; List parameters = []; List? passedArguments = null; if (arguments != null) passedArguments = [..arguments]; foreach (ParameterInfo parameter in constructorParameters) { object? fittingArgument = new InternalResolvePlaceholder(); if (passedArguments != null) { if (parameter.ParameterType.IsGenericParameter) { Type? genericArgument = type.GetGenericArguments().FirstOrDefault(a => a.Name.Equals(parameter.ParameterType.Name)); if (genericArgument is not null) { Type genericArgumentType = typeof(T).GetGenericArguments()[genericArgument.GenericParameterPosition]; fittingArgument = passedArguments.FirstOrGiven(a => a?.GetType() == genericArgumentType || genericArgumentType.IsInstanceOfType(a) || a is NullParameter nullParameter && genericArgumentType.IsAssignableFrom(nullParameter.ParameterType)); } } else { fittingArgument = passedArguments.FirstOrGiven(a => a?.GetType() == parameter.ParameterType || parameter.ParameterType.IsInstanceOfType(a) || a is NullParameter nullParameter && parameter.ParameterType.IsAssignableFrom(nullParameter.ParameterType)); } if (fittingArgument is not InternalResolvePlaceholder) passedArguments.Remove(fittingArgument); if (fittingArgument is NullParameter) fittingArgument = null; } if (fittingArgument is InternalResolvePlaceholder) { (bool success, object? resolvedObject, Exception? exception) = TryResolveNonGeneric(parameter.ParameterType, null, resolveStack); if (success) fittingArgument = resolvedObject; else if (!success && exception is not null) exceptions.Add(new ConstructorNotMatchingException(constructor, exception)); else throw new Exception("Invalid return value!"); if (fittingArgument is InternalResolvePlaceholder && passedArguments != null) { fittingArgument = passedArguments.FirstOrGiven(a => parameter.ParameterType.GetDefault() == a); if (fittingArgument is not InternalResolvePlaceholder) passedArguments.Remove(fittingArgument); } } if (fittingArgument is InternalResolvePlaceholder && parameter.HasDefaultValue) parameters.Add(parameter.DefaultValue); else parameters.Add(fittingArgument); } if (passedArguments == null || !passedArguments.Any()) return (!parameters.Any(p => p is InternalResolvePlaceholder), parameters, exceptions); exceptions.Add(new ConstructorNotMatchingException(constructor, new Exception("Not all given arguments were used!"))); return (false, parameters, exceptions); } /// /// Find the for the given /// /// The given /// The for the given private IRegistration? FindRegistration() => FindRegistration(typeof(T)); /// /// Find the for the given /// /// The given /// The for the given private IRegistration? FindRegistration(Type type) { IRegistration? registration = Registrations.FirstOrDefault(r => r.InterfaceType == type); if (registration != null) return registration; registration = Registrations.OfType().FirstOrDefault(r => r.ImplementationType == type); if (registration != null) return registration; //check for open generic registration if (!type.GenericTypeArguments.Any()) return null; List openGenericRegistrations = Registrations.Where(r => r.InterfaceType.ContainsGenericParameters).ToList(); return !openGenericRegistrations.Any() ? null : openGenericRegistrations.FirstOrDefault(r => r.InterfaceType == type.GetGenericTypeDefinition()); } /// /// Try to get the sorted constructors for the given /// /// The given /// A list of sorted for the given /// No public constructor was found for the given private List TryGetSortedConstructors(Type type) { List sortedConstructors = type.GetConstructors() .Where(c => !c.GetCustomAttributes().Any(a => _ignoreConstructorAttributes.Contains(a.GetType()))) .OrderByDescending(c => c.GetParameters().Length) .ToList(); if (!sortedConstructors.Any()) //no public constructor available throw new NoPublicConstructorFoundException(type); return sortedConstructors; } /// /// Get the implementation type for the given /// /// The given /// The given of the interface /// The implementation for the given /// Unknown passed private Type GetType(IRegistration registration) => registration switch { ITypedRegistration typedRegistration => typedRegistration.ImplementationType, ISingleTypeRegistration singleTypeRegistration => singleTypeRegistration.InterfaceType, _ => throw new UnknownRegistrationException($"Unknown registration used: {registration.GetType().Name}.") }; /// /// Check the given resolve stack for circular dependencies /// /// The given resolve stack /// The given /// The new resolve stack /// A circular dependency was detected private (bool success, List resolveStack, CircularDependencyException? exception) CheckForCircularDependencies(List? resolveStack) { if (resolveStack == null) //first resolve call resolveStack = [typeof(T)]; //create new stack and add the currently resolving type to the stack else if (resolveStack.Contains(typeof(T))) return (false, [], 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 return (true, resolveStack, null); } /// /// 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 not 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; /// /// Register a custom that can annotate a constructor to be ignored /// /// The custom /// The passed can't be used on a constructor public void RegisterIgnoreConstructorAttribute() where T : Attribute { AttributeUsageAttribute? attributeUsage = typeof(T).GetCustomAttribute(); if (attributeUsage == null || !attributeUsage.ValidOn.HasFlag(AttributeTargets.Constructor)) throw new InvalidIgnoreConstructorAttributeException(); _ignoreConstructorAttributes.Add(typeof(T)); } /// /// The method /// public void Dispose() { _singletons.Where(s => FindRegistration(s.type) is IWithDisposeStrategyInternal {DisposeStrategy: DisposeStrategy.Container}) .Select(s => s.instance) .OfType() .ForEach(d => d.Dispose()); _multitons.Where(m => FindRegistration(m.type) is IWithDisposeStrategyInternal {DisposeStrategy: DisposeStrategy.Container}) .SelectMany(m => m.instances) .Select(i => i.Value) .OfType() .ForEach(d => d.Dispose()); Registrations.Clear(); _singletons.Clear(); _multitons.Clear(); } }