From dd2e0fe4d7dbda62d421e2975f33945e10af1f56 Mon Sep 17 00:00:00 2001 From: Simon G Date: Wed, 8 Dec 2021 15:08:34 +0100 Subject: [PATCH] #51: get a constructor resolve stack before actually resolving parameters -> no unused parameters are generated while checking if a constructor can actually be used --- .../GenericMethodCaller.cs | 13 + LightweightIocContainer/IocContainer.cs | 253 ++++++++++++------ .../LightweightIocContainer.xml | 31 ++- .../Registrations/RegistrationBase.cs | 1 + .../InternalResolvePlaceholder.cs | 2 +- .../InternalToBeResolvedPlaceholder.cs | 24 ++ .../RegistrationBaseTest.cs | 1 + 7 files changed, 238 insertions(+), 87 deletions(-) rename LightweightIocContainer/{ => ResolvePlaceholders}/InternalResolvePlaceholder.cs (83%) create mode 100644 LightweightIocContainer/ResolvePlaceholders/InternalToBeResolvedPlaceholder.cs diff --git a/LightweightIocContainer/GenericMethodCaller.cs b/LightweightIocContainer/GenericMethodCaller.cs index bff301d..9663e34 100644 --- a/LightweightIocContainer/GenericMethodCaller.cs +++ b/LightweightIocContainer/GenericMethodCaller.cs @@ -41,5 +41,18 @@ namespace LightweightIocContainer throw ex.GetBaseException(); } } + + /// + /// Call a private generic method without generic type parameters + /// + /// The caller of the method + /// The name of the method to call + /// The generic parameter as parameter + /// The parameters of the method + /// The result of invoking the method + /// Could not find the generic method + /// Any thrown after invoking the generic method + public static object CallPrivate(object caller, string functionName, Type genericParameter, params object[] parameters) => + Call(caller, functionName, genericParameter, BindingFlags.NonPublic | BindingFlags.Instance, parameters); } } \ No newline at end of file diff --git a/LightweightIocContainer/IocContainer.cs b/LightweightIocContainer/IocContainer.cs index d583ca8..50bb495 100644 --- a/LightweightIocContainer/IocContainer.cs +++ b/LightweightIocContainer/IocContainer.cs @@ -15,6 +15,7 @@ using LightweightIocContainer.Interfaces.Installers; using LightweightIocContainer.Interfaces.Registrations; using LightweightIocContainer.Interfaces.Registrations.Fluent; using LightweightIocContainer.Registrations; +using LightweightIocContainer.ResolvePlaceholders; namespace LightweightIocContainer { @@ -266,8 +267,25 @@ namespace LightweightIocContainer /// 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); + GenericMethodCaller.CallPrivate(this, nameof(ResolveInternal), type, arguments, resolveStack); + private object Resolve(InternalToBeResolvedPlaceholder toBeResolvedPlaceholder, List resolveStack) + { + if (toBeResolvedPlaceholder.Parameters == null) + return Resolve(toBeResolvedPlaceholder.ResolvedType, null, resolveStack); + + List parameters = new(); + foreach (object parameter in toBeResolvedPlaceholder.Parameters) + { + if (parameter is InternalToBeResolvedPlaceholder internalToBeResolvedPlaceholder) + parameters.Add(Resolve(internalToBeResolvedPlaceholder, resolveStack)); + else + parameters.Add(parameter); + } + + return Resolve(toBeResolvedPlaceholder.ResolvedType, parameters.ToArray(), resolveStack); + } + /// /// Gets an instance of a given registered /// @@ -282,12 +300,7 @@ namespace LightweightIocContainer IRegistration registration = FindRegistration() ?? 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 + resolveStack = CheckForCircularDependencies(resolveStack); T resolvedInstance = registration switch { @@ -313,15 +326,9 @@ namespace LightweightIocContainer /// 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; + Type type = GetType(registration); + + object instance = TryGetSingletonInstance(type); if (instance != null) return (T) instance; @@ -331,6 +338,8 @@ namespace LightweightIocContainer return newInstance; } + + private object TryGetSingletonInstance(Type type) => _singletons.FirstOrDefault(s => s.type == type).instance; //if a singleton instance exists return it /// /// Gets or creates a multiton instance of a given @@ -390,7 +399,7 @@ namespace LightweightIocContainer T instance; if (registration is IOpenGenericRegistration openGenericRegistration) { - arguments = ResolveConstructorArguments(openGenericRegistration.ImplementationType, arguments, resolveStack); + arguments = ResolveTypeCreationArguments(openGenericRegistration.ImplementationType, arguments, resolveStack); //create generic implementation type from generic arguments of T Type genericImplementationType = openGenericRegistration.ImplementationType.MakeGenericType(typeof(T).GenericTypeArguments); @@ -399,7 +408,7 @@ namespace LightweightIocContainer } else if (registration is ITypedRegistration defaultRegistration) { - arguments = ResolveConstructorArguments(defaultRegistration.ImplementationType, arguments, resolveStack); + arguments = ResolveTypeCreationArguments(defaultRegistration.ImplementationType, arguments, resolveStack); instance = (T) Activator.CreateInstance(defaultRegistration.ImplementationType, arguments); } else if (registration is ISingleTypeRegistration singleTypeRegistration) @@ -409,7 +418,7 @@ namespace LightweightIocContainer if (singleTypeRegistration.FactoryMethod == null) //type registration without interface -> just create this type { - arguments = ResolveConstructorArguments(singleTypeRegistration.InterfaceType, arguments, resolveStack); + arguments = ResolveTypeCreationArguments(singleTypeRegistration.InterfaceType, arguments, resolveStack); instance = (T)Activator.CreateInstance(singleTypeRegistration.InterfaceType, arguments); } else //factory method set to create the instance @@ -468,7 +477,7 @@ namespace LightweightIocContainer } /// - /// Resolve the missing constructor arguments + /// Resolve the missing type creation arguments /// /// The that will be created /// The existing arguments @@ -476,101 +485,187 @@ namespace LightweightIocContainer /// 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) + private object[] ResolveTypeCreationArguments(Type type, object[] arguments, List resolveStack) { + NoMatchingConstructorFoundException noMatchingConstructorFoundException = null; + //find best ctor - List sortedConstructors = type.GetConstructors().OrderByDescending(c => c.GetParameters().Length).ToList(); - if (!sortedConstructors.Any()) //no public constructor available - throw new NoPublicConstructorFoundException(type); + List sortedConstructors = TryGetSortedConstructors(type); + foreach (ConstructorInfo constructor in sortedConstructors) + { + (bool result, List parameters, List exceptions) = TryGetConstructorResolveStack(constructor, arguments, resolveStack); - NoMatchingConstructorFoundException noMatchingConstructorFoundException = null; + if (result) + { + if (parameters == null) + return null; + + List constructorParameters = new(); + foreach (object parameter in parameters) + { + if (parameter is InternalToBeResolvedPlaceholder toBeResolvedPlaceholder) + constructorParameters.Add(Resolve(toBeResolvedPlaceholder, resolveStack)); + else + constructorParameters.Add(parameter); + } + + return constructorParameters.ToArray(); + } + + noMatchingConstructorFoundException ??= new NoMatchingConstructorFoundException(type); + exceptions.ForEach(e => + noMatchingConstructorFoundException.AddInnerException(new ConstructorNotMatchingException(constructor, e))); + } + + if (noMatchingConstructorFoundException != null) + throw noMatchingConstructorFoundException; + + return null; + } - foreach (ConstructorInfo ctor in sortedConstructors) + private (bool result, List parameters, List constructorNotMatchingExceptions) TryGetConstructorResolveStack(ConstructorInfo constructor, object[] arguments, List resolveStack) + { + List constructorParameters = constructor.GetParameters().ToList(); + if (!constructorParameters.Any()) + return (true, null, null); + + List constructorNotMatchingExceptions = new(); + List parameters = new(); + + List passedArguments = null; + if (arguments != null) + passedArguments = new List(arguments); + + foreach (ParameterInfo parameter in constructorParameters) { - try + object fittingArgument = new InternalResolvePlaceholder(); + if (passedArguments != null) { - List argumentsList = arguments?.ToList(); - List ctorParams = new(); + fittingArgument = passedArguments.FirstOrGiven(a => + a?.GetType() == parameter.ParameterType || parameter.ParameterType.IsInstanceOfType(a)); + + if (fittingArgument is not InternalResolvePlaceholder) + { + int index = passedArguments.IndexOf(fittingArgument); //todo + passedArguments[index] = new InternalResolvePlaceholder(); + } + } - ParameterInfo[] parameters = ctor.GetParameters(); - foreach (ParameterInfo parameter in parameters) + if (fittingArgument is InternalResolvePlaceholder) + { + try { - object fittingArgument = new InternalResolvePlaceholder(); - if (argumentsList != null) + IRegistration registration = FindRegistration(parameter.ParameterType) ?? throw new TypeNotRegisteredException(parameter.ParameterType); + + List internalResolveStack = new(resolveStack); + internalResolveStack = CheckForCircularDependencies(parameter.ParameterType, internalResolveStack); //testMe: seems to work + + Type registeredType = GetTypeNonGeneric(parameter.ParameterType, registration); + + object singletonInstance = TryGetSingletonInstance(registeredType); + if (singletonInstance != null) + fittingArgument = singletonInstance; + else { - 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 + object[] argumentsForRegistration = null; + if (registration is IWithParametersInternal { Parameters: { } } registrationWithParameters) + argumentsForRegistration = UpdateArgumentsWithRegistrationParameters(registrationWithParameters, null); + + foreach (ConstructorInfo registeredTypeConstructor in TryGetSortedConstructors(registeredType)) { - try + (bool result, List parametersToResolve, List exceptions) = + TryGetConstructorResolveStack(registeredTypeConstructor, argumentsForRegistration, internalResolveStack); + + if (result) { - fittingArgument = Resolve(parameter.ParameterType, null, resolveStack); + fittingArgument = new InternalToBeResolvedPlaceholder(registeredType, parametersToResolve); + break; } - catch (Exception) - { - fittingArgument = argumentsList.FirstOrGiven(a => parameter.ParameterType.GetDefault() == a); - if (fittingArgument is not InternalResolvePlaceholder) - { - int index = argumentsList.IndexOf(fittingArgument); - argumentsList[index] = new InternalResolvePlaceholder(); - } - } + constructorNotMatchingExceptions.AddRange(exceptions); } } - - 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); + } + catch (Exception exception) + { + constructorNotMatchingExceptions.Add(new ConstructorNotMatchingException(constructor, exception)); } - 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 (fittingArgument is InternalResolvePlaceholder && passedArguments != null) + { + fittingArgument = passedArguments.FirstOrGiven(a => parameter.ParameterType.GetDefault() == a); + + if (fittingArgument is not InternalResolvePlaceholder) + { + int index = passedArguments.IndexOf(fittingArgument); + passedArguments[index] = new InternalResolvePlaceholder(); + } + } } + + if (fittingArgument is InternalResolvePlaceholder && parameter.HasDefaultValue) + parameters.Add(parameter.DefaultValue); + else + parameters.Add(fittingArgument); } - if (noMatchingConstructorFoundException != null) - throw noMatchingConstructorFoundException; - - return null; + return (!parameters.Any(p => p is InternalResolvePlaceholder), parameters, constructorNotMatchingExceptions); } [CanBeNull] - private IRegistration FindRegistration() + private IRegistration FindRegistration() => FindRegistration(typeof(T)); + + [CanBeNull] + private IRegistration FindRegistration(Type type) { - IRegistration registration = Registrations.FirstOrDefault(r => r.InterfaceType == typeof(T)); + IRegistration registration = Registrations.FirstOrDefault(r => r.InterfaceType == type); if (registration != null) return registration; - registration = Registrations.OfType().FirstOrDefault(r => r.ImplementationType == typeof(T)); + registration = Registrations.OfType().FirstOrDefault(r => r.ImplementationType == type); if (registration != null) return registration; //check for open generic registration - if (!typeof(T).GenericTypeArguments.Any()) + if (!type.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()); + return !openGenericRegistrations.Any() ? null : openGenericRegistrations.FirstOrDefault(r => r.InterfaceType == type.GetGenericTypeDefinition()); + } + + private List TryGetSortedConstructors(Type type) + { + List sortedConstructors = type.GetConstructors().OrderByDescending(c => c.GetParameters().Length).ToList(); + if (!sortedConstructors.Any()) //no public constructor available + throw new NoPublicConstructorFoundException(type); + + return sortedConstructors; } + private Type GetType(IRegistration registration) => + registration switch + { + ITypedRegistration typedRegistration => typedRegistration.ImplementationType, + ISingleTypeRegistration singleTypeRegistration => singleTypeRegistration.InterfaceType, + _ => throw new UnknownRegistrationException($"Unknown registration used: {registration.GetType().Name}.") + }; + + private Type GetTypeNonGeneric(Type type, IRegistration registration) => (Type) GenericMethodCaller.CallPrivate(this, nameof(GetType), type, registration); + + private List CheckForCircularDependencies(List resolveStack) => CheckForCircularDependencies(typeof(T), resolveStack); + private List CheckForCircularDependencies(Type type, List resolveStack) + { + if (resolveStack == null) //first resolve call + resolveStack = new List {type}; //create new stack and add the currently resolving type to the stack + else if (resolveStack.Contains(type)) + throw new CircularDependencyException(type, 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(type); //add currently resolving type to the stack + + return resolveStack; + } + /// /// Clear the multiton instances of the given from the registered multitons list /// diff --git a/LightweightIocContainer/LightweightIocContainer.xml b/LightweightIocContainer/LightweightIocContainer.xml index 7be92cf..e656be4 100644 --- a/LightweightIocContainer/LightweightIocContainer.xml +++ b/LightweightIocContainer/LightweightIocContainer.xml @@ -331,6 +331,18 @@ Could not find the generic method Any thrown after invoking the generic method + + + Call a private generic method without generic type parameters + + The caller of the method + The name of the method to call + The generic parameter as parameter + The parameters of the method + The result of invoking the method + Could not find the generic method + Any thrown after invoking the generic method + An that installs all s for its given @@ -777,11 +789,6 @@ A that implements a - - - An internal placeholder that is used during the resolving process - - The main container that carries all the s and can resolve all the types you'll ever want @@ -990,9 +997,9 @@ The constructor arguments The argument list updated with the - + - Resolve the missing constructor arguments + Resolve the missing type creation arguments The that will be created The existing arguments @@ -1497,6 +1504,16 @@ The The current instance of this + + + An internal placeholder that is used during the resolving process + + + + + An internal placeholder that is used to hold types that need to be resolved during the resolving process + + Returns the default value for a given diff --git a/LightweightIocContainer/Registrations/RegistrationBase.cs b/LightweightIocContainer/Registrations/RegistrationBase.cs index a57a28b..63481e6 100644 --- a/LightweightIocContainer/Registrations/RegistrationBase.cs +++ b/LightweightIocContainer/Registrations/RegistrationBase.cs @@ -10,6 +10,7 @@ using LightweightIocContainer.Interfaces.Factories; using LightweightIocContainer.Interfaces.Installers; using LightweightIocContainer.Interfaces.Registrations; using LightweightIocContainer.Interfaces.Registrations.Fluent; +using LightweightIocContainer.ResolvePlaceholders; namespace LightweightIocContainer.Registrations { diff --git a/LightweightIocContainer/InternalResolvePlaceholder.cs b/LightweightIocContainer/ResolvePlaceholders/InternalResolvePlaceholder.cs similarity index 83% rename from LightweightIocContainer/InternalResolvePlaceholder.cs rename to LightweightIocContainer/ResolvePlaceholders/InternalResolvePlaceholder.cs index 8f97059..4bfc7e9 100644 --- a/LightweightIocContainer/InternalResolvePlaceholder.cs +++ b/LightweightIocContainer/ResolvePlaceholders/InternalResolvePlaceholder.cs @@ -2,7 +2,7 @@ // Created: 2019-11-22 // Copyright(c) 2019 SimonG. All Rights Reserved. -namespace LightweightIocContainer +namespace LightweightIocContainer.ResolvePlaceholders { /// /// An internal placeholder that is used during the resolving process diff --git a/LightweightIocContainer/ResolvePlaceholders/InternalToBeResolvedPlaceholder.cs b/LightweightIocContainer/ResolvePlaceholders/InternalToBeResolvedPlaceholder.cs new file mode 100644 index 0000000..73d819a --- /dev/null +++ b/LightweightIocContainer/ResolvePlaceholders/InternalToBeResolvedPlaceholder.cs @@ -0,0 +1,24 @@ +// Author: Gockner, Simon +// Created: 2021-12-08 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +using System; +using System.Collections.Generic; + +namespace LightweightIocContainer.ResolvePlaceholders +{ + /// + /// An internal placeholder that is used to hold types that need to be resolved during the resolving process + /// + internal class InternalToBeResolvedPlaceholder + { + public InternalToBeResolvedPlaceholder(Type resolvedType, List parameters) + { + ResolvedType = resolvedType; + Parameters = parameters; + } + + public Type ResolvedType { get; } + public List Parameters { get; } + } +} \ No newline at end of file diff --git a/Test.LightweightIocContainer/RegistrationBaseTest.cs b/Test.LightweightIocContainer/RegistrationBaseTest.cs index 81cc84a..6776230 100644 --- a/Test.LightweightIocContainer/RegistrationBaseTest.cs +++ b/Test.LightweightIocContainer/RegistrationBaseTest.cs @@ -6,6 +6,7 @@ using JetBrains.Annotations; using LightweightIocContainer; using LightweightIocContainer.Exceptions; using LightweightIocContainer.Registrations; +using LightweightIocContainer.ResolvePlaceholders; using Moq; using NUnit.Framework;