diff --git a/LightweightIocContainer/Exceptions/GenericMethodNotFoundException.cs b/LightweightIocContainer/Exceptions/GenericMethodNotFoundException.cs new file mode 100644 index 0000000..1438b54 --- /dev/null +++ b/LightweightIocContainer/Exceptions/GenericMethodNotFoundException.cs @@ -0,0 +1,24 @@ +// Author: Simon Gockner +// Created: 2020-09-18 +// Copyright(c) 2020 SimonG. All Rights Reserved. + +using System; + +namespace LightweightIocContainer.Exceptions +{ + /// + /// Could not find generic method + /// + public class GenericMethodNotFoundException : Exception + { + /// + /// Could not find generic method + /// + /// The name of the generic method + public GenericMethodNotFoundException(string functionName) + : base($"Could not find function {functionName}") + { + + } + } +} \ No newline at end of file diff --git a/LightweightIocContainer/GenericMethodCaller.cs b/LightweightIocContainer/GenericMethodCaller.cs new file mode 100644 index 0000000..bff301d --- /dev/null +++ b/LightweightIocContainer/GenericMethodCaller.cs @@ -0,0 +1,45 @@ +// Author: Simon Gockner +// Created: 2020-09-18 +// Copyright(c) 2020 SimonG. All Rights Reserved. + +using System; +using System.Reflection; +using LightweightIocContainer.Exceptions; + +namespace LightweightIocContainer +{ + /// + /// Helper class to call a generic method without generic type parameters + /// + internal static class GenericMethodCaller + { + /// + /// Call a generic method without generic type parameters + /// + /// The caller of the method + /// The name of the method to call + /// The generic parameter as parameter + /// The to find the method + /// 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 Call(object caller, string functionName, Type genericParameter, BindingFlags bindingFlags, params object[] parameters) + { + MethodInfo method = caller.GetType().GetMethod(functionName, bindingFlags); + MethodInfo genericMethod = method?.MakeGenericMethod(genericParameter); + + if (genericMethod == null) + throw new GenericMethodNotFoundException(functionName); + + try //exceptions thrown by methods called with invoke are wrapped into another exception, the exception thrown by the invoked method can be returned by `Exception.GetBaseException()` + { + return genericMethod.Invoke(caller, parameters); + } + catch (Exception ex) + { + throw ex.GetBaseException(); + } + } + } +} \ No newline at end of file diff --git a/LightweightIocContainer/Interfaces/IIocContainer.cs b/LightweightIocContainer/Interfaces/IIocContainer.cs index 62a4434..d4c1465 100644 --- a/LightweightIocContainer/Interfaces/IIocContainer.cs +++ b/LightweightIocContainer/Interfaces/IIocContainer.cs @@ -29,6 +29,15 @@ namespace LightweightIocContainer.Interfaces /// The created IDefaultRegistration Register(Lifestyle lifestyle = Lifestyle.Transient) where TImplementation : TInterface; + /// + /// 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 + IOpenGenericRegistration RegisterOpenGenerics(Type tInterface, Type tImplementation, Lifestyle lifestyle = Lifestyle.Transient); + /// /// Register multiple interfaces for a that implements them /// diff --git a/LightweightIocContainer/Interfaces/Registrations/IOpenGenericRegistration.cs b/LightweightIocContainer/Interfaces/Registrations/IOpenGenericRegistration.cs new file mode 100644 index 0000000..4aaebc4 --- /dev/null +++ b/LightweightIocContainer/Interfaces/Registrations/IOpenGenericRegistration.cs @@ -0,0 +1,19 @@ +// Author: Simon Gockner +// Created: 2020-09-18 +// Copyright(c) 2020 SimonG. All Rights Reserved. + +using System; + +namespace LightweightIocContainer.Interfaces.Registrations +{ + /// + /// for open generic types + /// + public interface IOpenGenericRegistration : IRegistration, ILifestyleProvider + { + /// + /// The that implements the that is registered with this + /// + Type ImplementationType { get; } + } +} \ No newline at end of file diff --git a/LightweightIocContainer/IocContainer.cs b/LightweightIocContainer/IocContainer.cs index 6f114be..c95a555 100644 --- a/LightweightIocContainer/IocContainer.cs +++ b/LightweightIocContainer/IocContainer.cs @@ -68,6 +68,29 @@ namespace LightweightIocContainer 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 /// @@ -246,20 +269,7 @@ namespace LightweightIocContainer /// Could not find function private object Resolve(Type type, object[] arguments, List resolveStack) { - MethodInfo resolveMethod = typeof(IocContainer).GetMethod(nameof(ResolveInternal), BindingFlags.NonPublic | BindingFlags.Instance); - MethodInfo genericResolveMethod = resolveMethod?.MakeGenericMethod(type); - - if (genericResolveMethod == null) - throw new InternalResolveException($"Could not find function {nameof(ResolveInternal)}"); - - try //exceptions thrown by methods called with invoke are wrapped into another exception, the exception thrown by the invoked method can be returned by `Exception.GetBaseException()` - { - return genericResolveMethod.Invoke(this, new object[] { arguments, resolveStack }); - } - catch (Exception ex) - { - throw ex.GetBaseException(); - } + return GenericMethodCaller.Call(this, nameof(ResolveInternal), type, BindingFlags.NonPublic | BindingFlags.Instance, arguments, resolveStack); } /// @@ -273,7 +283,7 @@ namespace LightweightIocContainer /// The registration for the given has an unknown private T ResolveInternal(object[] arguments, List resolveStack = null) { - IRegistration registration = _registrations.FirstOrDefault(r => r.InterfaceType == typeof(T)); + IRegistration registration = FindRegistration(); if (registration == null) throw new TypeNotRegisteredException(typeof(T)); @@ -290,16 +300,23 @@ namespace LightweightIocContainer if (registration is IRegistrationBase defaultRegistration) { if (defaultRegistration.Lifestyle == Lifestyle.Singleton) - resolvedInstance = GetOrCreateSingletonInstance(defaultRegistration, arguments, resolveStack); + resolvedInstance = GetOrCreateSingletonInstance(defaultRegistration, arguments, resolveStack); else if (defaultRegistration is IMultitonRegistration multitonRegistration && defaultRegistration.Lifestyle == Lifestyle.Multiton) resolvedInstance = GetOrCreateMultitonInstance(multitonRegistration, arguments, resolveStack); else - resolvedInstance = CreateInstance(defaultRegistration, arguments, resolveStack); + resolvedInstance = CreateInstance(defaultRegistration, arguments, resolveStack); } else if (registration is ITypedFactoryRegistration typedFactoryRegistration) { resolvedInstance = typedFactoryRegistration.Factory.Factory; } + else if (registration is IOpenGenericRegistration openGenericRegistration) + { + if (openGenericRegistration.Lifestyle == Lifestyle.Singleton) + resolvedInstance = GetOrCreateSingletonInstance(openGenericRegistration, arguments, resolveStack); + else + resolvedInstance = CreateInstance(openGenericRegistration, arguments, resolveStack); + } else throw new UnknownRegistrationException($"There is no registration of type {registration.GetType().Name}."); @@ -316,13 +333,15 @@ namespace LightweightIocContainer /// The arguments to resolve /// The current resolve stack /// An existing or newly created singleton instance of the given - private T GetOrCreateSingletonInstance(IRegistrationBase registration, object[] arguments, List resolveStack) + private T GetOrCreateSingletonInstance(IRegistration registration, object[] arguments, List resolveStack) { Type type; if (registration is ITypedRegistrationBase typedRegistration) type = typedRegistration.ImplementationType; else if (registration is ISingleTypeRegistration singleTypeRegistration) type = singleTypeRegistration.InterfaceType; + else if (registration is IOpenGenericRegistration openGenericRegistration) + type = openGenericRegistration.ImplementationType; else throw new UnknownRegistrationException($"There is no registration {registration.GetType().Name} that can have lifestyle singleton."); @@ -332,7 +351,7 @@ namespace LightweightIocContainer 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); + T newInstance = CreateInstance(registration, arguments, resolveStack); _singletons.Add((type, newInstance)); return newInstance; @@ -364,13 +383,13 @@ namespace LightweightIocContainer if (instances.TryGetValue(scopeArgument, out object instance)) return (T) instance; - T createdInstance = CreateInstance(registration, arguments, resolveStack); + T createdInstance = CreateInstance(registration, arguments, resolveStack); instances.Add(scopeArgument, createdInstance); return createdInstance; } - T newInstance = CreateInstance(registration, arguments, resolveStack); + T newInstance = CreateInstance(registration, arguments, resolveStack); ConditionalWeakTable weakTable = new ConditionalWeakTable(); weakTable.Add(scopeArgument, newInstance); @@ -388,10 +407,10 @@ namespace LightweightIocContainer /// The constructor arguments /// The current resolve stack /// A newly created instance of the given - private T CreateInstance(IRegistrationBase registration, object[] arguments, List resolveStack) + private T CreateInstance(IRegistration registration, object[] arguments, List resolveStack) { - if (registration.Parameters != null) - arguments = UpdateArgumentsWithRegistrationParameters(registration, arguments); + if (registration is IWithParameters registrationWithParameters && registrationWithParameters.Parameters != null) + arguments = UpdateArgumentsWithRegistrationParameters(registrationWithParameters, arguments); T instance; if (registration is ITypedRegistrationBase defaultRegistration) @@ -412,6 +431,15 @@ namespace LightweightIocContainer else //factory method set to create the instance instance = singleTypeRegistration.FactoryMethod(this); } + else 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 throw new UnknownRegistrationException($"There is no registration of type {registration.GetType().Name}."); @@ -551,6 +579,24 @@ namespace LightweightIocContainer return null; } + [CanBeNull] + private IRegistration FindRegistration() + { + IRegistration registration = _registrations.FirstOrDefault(r => r.InterfaceType == 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(); + if (!openGenericRegistrations.Any()) + return null; + + return openGenericRegistrations.FirstOrDefault(r => r.InterfaceType == typeof(T).GetGenericTypeDefinition()); + } + /// /// Clear the multiton instances of the given from the registered multitons list /// @@ -571,7 +617,7 @@ namespace LightweightIocContainer /// /// The given /// True if the given is registered with this , false if not - public bool IsTypeRegistered() => _registrations.Any(registration => registration.InterfaceType == typeof(T)); + public bool IsTypeRegistered() => FindRegistration() != null; /// /// The method diff --git a/LightweightIocContainer/LightweightIocContainer.xml b/LightweightIocContainer/LightweightIocContainer.xml index b25a486..a2891e6 100644 --- a/LightweightIocContainer/LightweightIocContainer.xml +++ b/LightweightIocContainer/LightweightIocContainer.xml @@ -86,6 +86,17 @@ The constructor that does not match + + + Could not find generic method + + + + + Could not find generic method + + The name of the generic method + The creation of the abstract method is illegal in its current state @@ -274,6 +285,24 @@ The implemented abstract typed factory/> + + + Helper class to call a generic method without generic type parameters + + + + + Call a generic method without generic type parameters + + The caller of the method + The name of the method to call + The generic parameter as parameter + The to find the method + 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 @@ -346,6 +375,15 @@ The for this The created + + + 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 + Register multiple interfaces for a that implements them @@ -609,6 +647,16 @@ The registered interface The registered implementation + + + for open generic types + + + + + The that implements the that is registered with this + + The base registration that is used to register an Interface @@ -705,6 +753,17 @@ The for this The created + + + 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 + Register multiple interfaces for a that implements them @@ -826,7 +885,7 @@ The given is not registered in this The registration for the given has an unknown - + Gets or creates a singleton instance of a given @@ -848,7 +907,7 @@ No arguments given Scope argument not given - + Creates an instance of a given @@ -1078,6 +1137,39 @@ The of the multiton scope + + + for open generic types + + + + + for open generic types + + The of the interface + The of the implementation type + The of this + + + + The name of the + + + + + The of the Interface that is registered with this + + + + + The that implements the that is registered with this + + + + + The Lifestyle of Instances that are created with this + + The that is used to register an Interface diff --git a/LightweightIocContainer/Properties/AssemblyInfo.cs b/LightweightIocContainer/Properties/AssemblyInfo.cs index 581d540..18bea16 100644 --- a/LightweightIocContainer/Properties/AssemblyInfo.cs +++ b/LightweightIocContainer/Properties/AssemblyInfo.cs @@ -4,8 +4,4 @@ using System.Runtime.CompilerServices; -[assembly:InternalsVisibleTo("Test.LightweightIocContainer")] - -namespace LightweightIocContainer.Properties -{ -} \ No newline at end of file +[assembly:InternalsVisibleTo("Test.LightweightIocContainer")] \ No newline at end of file diff --git a/LightweightIocContainer/Registrations/OpenGenericRegistration.cs b/LightweightIocContainer/Registrations/OpenGenericRegistration.cs new file mode 100644 index 0000000..df37b4f --- /dev/null +++ b/LightweightIocContainer/Registrations/OpenGenericRegistration.cs @@ -0,0 +1,50 @@ +// Author: Simon Gockner +// Created: 2020-09-18 +// Copyright(c) 2020 SimonG. All Rights Reserved. + +using System; +using LightweightIocContainer.Interfaces.Registrations; + +namespace LightweightIocContainer.Registrations +{ + /// + /// for open generic types + /// + public class OpenGenericRegistration : IOpenGenericRegistration + { + /// + /// for open generic types + /// + /// The of the interface + /// The of the implementation type + /// The of this + public OpenGenericRegistration(Type interfaceType, Type implementationType, Lifestyle lifestyle) + { + InterfaceType = interfaceType; + ImplementationType = implementationType; + Lifestyle = lifestyle; + + Name = $"{InterfaceType.Name}, {ImplementationType.Name}, Lifestyle: {Lifestyle.ToString()}"; + } + + /// + /// The name of the + /// + public string Name { get; } + + /// + /// The of the Interface that is registered with this + /// + public Type InterfaceType { get; } + + /// + /// The that implements the that is registered with this + /// + public Type ImplementationType { get; } + + /// + /// The Lifestyle of Instances that are created with this + /// + public Lifestyle Lifestyle { get; } + } +} \ No newline at end of file diff --git a/LightweightIocContainer/Registrations/RegistrationFactory.cs b/LightweightIocContainer/Registrations/RegistrationFactory.cs index f87383c..7ff2487 100644 --- a/LightweightIocContainer/Registrations/RegistrationFactory.cs +++ b/LightweightIocContainer/Registrations/RegistrationFactory.cs @@ -33,6 +33,11 @@ namespace LightweightIocContainer.Registrations return new DefaultRegistration(typeof(TInterface), typeof(TImplementation), lifestyle); } + public IOpenGenericRegistration Register(Type tInterface, Type tImplementation, Lifestyle lifestyle) + { + return new OpenGenericRegistration(tInterface, tImplementation, lifestyle); + } + /// /// Register multiple interfaces for a that implements them and create a /// diff --git a/Test.LightweightIocContainer/EnumerableExtensionTest.cs b/Test.LightweightIocContainer/EnumerableExtensionTest.cs index ddc724c..0540ac2 100644 --- a/Test.LightweightIocContainer/EnumerableExtensionTest.cs +++ b/Test.LightweightIocContainer/EnumerableExtensionTest.cs @@ -3,6 +3,7 @@ // Copyright(c) 2019 SimonG. All Rights Reserved. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using LightweightIocContainer; using NUnit.Framework; @@ -27,6 +28,7 @@ namespace Test.LightweightIocContainer [Test] + [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")] public void TestFirstOrGivenNoPredicateEmpty() { List list = new List(); @@ -51,6 +53,7 @@ namespace Test.LightweightIocContainer } [Test] + [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")] public void TestFirstOrGivenPredicateEmpty() { List list = new List(); diff --git a/Test.LightweightIocContainer/IocContainerRecursionTest.cs b/Test.LightweightIocContainer/IocContainerRecursionTest.cs index 9a0c069..0e3e772 100644 --- a/Test.LightweightIocContainer/IocContainerRecursionTest.cs +++ b/Test.LightweightIocContainer/IocContainerRecursionTest.cs @@ -141,6 +141,7 @@ namespace Test.LightweightIocContainer _iocContainer.Register(); IA a = _iocContainer.Resolve(); + Assert.IsNotNull(a); } [Test] diff --git a/Test.LightweightIocContainer/OpenGenericRegistrationTest.cs b/Test.LightweightIocContainer/OpenGenericRegistrationTest.cs new file mode 100644 index 0000000..e1b87d7 --- /dev/null +++ b/Test.LightweightIocContainer/OpenGenericRegistrationTest.cs @@ -0,0 +1,70 @@ +// Author: Simon Gockner +// Created: 2020-09-18 +// Copyright(c) 2020 SimonG. All Rights Reserved. + +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using LightweightIocContainer; +using LightweightIocContainer.Exceptions; +using LightweightIocContainer.Interfaces; +using NUnit.Framework; + +namespace Test.LightweightIocContainer +{ + [TestFixture] + public class OpenGenericRegistrationTest + { + private IIocContainer _iocContainer; + + [UsedImplicitly] + [SuppressMessage("ReSharper", "UnusedTypeParameter")] + public interface ITest + { + + } + + [UsedImplicitly] + public class Test : ITest + { + + } + + [SetUp] + public void SetUp() => _iocContainer = new IocContainer(); + + [TearDown] + public void TearDown() => _iocContainer.Dispose(); + + [Test] + public void TestRegisterOpenGenericType() + { + _iocContainer.RegisterOpenGenerics(typeof(ITest<>), typeof(Test<>)); + + ITest test = _iocContainer.Resolve>(); + Assert.NotNull(test); + } + + [Test] + public void TestRegisterOpenGenericTypeAsSingleton() + { + _iocContainer.RegisterOpenGenerics(typeof(ITest<>), typeof(Test<>), Lifestyle.Singleton); + + ITest test = _iocContainer.Resolve>(); + Assert.NotNull(test); + + ITest secondTest = _iocContainer.Resolve>(); + Assert.NotNull(secondTest); + + Assert.AreEqual(test, secondTest); + Assert.AreSame(test, secondTest); + } + + [Test] + public void TestRegisterOpenGenericTypeAsMultitonThrowsException() + => Assert.Throws(() => _iocContainer.RegisterOpenGenerics(typeof(ITest<>), typeof(Test<>), Lifestyle.Multiton)); + + [Test] + public void TestRegisterNonOpenGenericTypeWithOpenGenericsFunctionThrowsException() + => Assert.Throws(() => _iocContainer.RegisterOpenGenerics(typeof(int), typeof(int))); + } +} \ No newline at end of file