diff --git a/LightweightIocContainer.sln.DotSettings b/LightweightIocContainer.sln.DotSettings index d75d405..3278b56 100644 --- a/LightweightIocContainer.sln.DotSettings +++ b/LightweightIocContainer.sln.DotSettings @@ -2,4 +2,6 @@ // Author: $USER_NAME$ // Created: $CREATED_YEAR$-$CREATED_MONTH$-$CREATED_DAY$ // Copyright(c) $CREATED_YEAR$ SimonG. All Rights Reserved. - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> \ No newline at end of file + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True \ No newline at end of file diff --git a/LightweightIocContainer/Exceptions/MultitonResolveException.cs b/LightweightIocContainer/Exceptions/MultitonResolveException.cs new file mode 100644 index 0000000..057693f --- /dev/null +++ b/LightweightIocContainer/Exceptions/MultitonResolveException.cs @@ -0,0 +1,25 @@ +// // Author: Gockner, Simon +// // Created: 2019-06-07 +// // Copyright(c) 2019 SimonG. All Rights Reserved. + +using System; + +namespace LightweightIocContainer.Exceptions +{ + /// + /// An error happened while trying to resolve a multiton + /// + public class MultitonResolveException : InternalResolveException + { + public MultitonResolveException(string message, Type type) + : base(message) + { + Type = type; + } + + /// + /// The type of the multiton that's responsible for the exception + /// + public Type Type { get; } + } +} \ No newline at end of file diff --git a/LightweightIocContainer/Interfaces/Registrations/IMultitonRegistration.cs b/LightweightIocContainer/Interfaces/Registrations/IMultitonRegistration.cs new file mode 100644 index 0000000..54927e9 --- /dev/null +++ b/LightweightIocContainer/Interfaces/Registrations/IMultitonRegistration.cs @@ -0,0 +1,20 @@ +// // Author: Gockner, Simon +// // Created: 2019-06-07 +// // Copyright(c) 2019 SimonG. All Rights Reserved. + +using System; + +namespace LightweightIocContainer.Interfaces.Registrations +{ + /// + /// The registration that is used to register a multiton + /// + /// The registered interface + public interface IMultitonRegistration : IDefaultRegistration + { + /// + /// The type of the multiton scope + /// + Type Scope { get; } + } +} \ No newline at end of file diff --git a/LightweightIocContainer/IocContainer.cs b/LightweightIocContainer/IocContainer.cs index e1378f2..9766856 100644 --- a/LightweightIocContainer/IocContainer.cs +++ b/LightweightIocContainer/IocContainer.cs @@ -19,6 +19,7 @@ namespace LightweightIocContainer { private readonly List _registrations = new List(); private readonly List<(Type type, object instance)> _singletons = new List<(Type, object)>(); //TODO: Think about the usage of ConditionalWeakTable<> + private readonly List<(Type type, Type scope, List<(object scopeInstance, object instance)> instances)> _multitons = new List<(Type, Type, List<(object, object)>)>(); /// /// Install the given installers for the current @@ -106,6 +107,8 @@ namespace LightweightIocContainer { if (defaultRegistration.Lifestyle == Lifestyle.Singleton) return GetOrCreateSingletonInstance(defaultRegistration, arguments); + else if (defaultRegistration is IMultitonRegistration multitonRegistration && defaultRegistration.Lifestyle == Lifestyle.Multiton) + return GetOrCreateMultitonInstance(multitonRegistration, arguments); return CreateInstance(defaultRegistration, arguments); } @@ -122,7 +125,7 @@ namespace LightweightIocContainer /// /// The given type /// The registration of the given type - /// The constructor arguments + /// The arguments to resolve /// An existing or newly created singleton instance of the given type private T GetOrCreateSingletonInstance(IDefaultRegistration registration, params object[] arguments) { @@ -138,6 +141,44 @@ namespace LightweightIocContainer return newInstance; } + /// + /// Gets or creates a multiton instance of a given type + /// + /// The given type + /// The registration of the given type + /// The arguments to resolve + /// An existing or newly created multiton instance of the given type + /// No arguments given + /// Scope argument not given + private T GetOrCreateMultitonInstance(IMultitonRegistration registration, params object[] arguments) + { + 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) + 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 == typeof(T) && m.scope == registration.Scope).instances; + if (instances != null && instances.Any()) + { + var instance = instances.FirstOrDefault(i => i.scopeInstance.Equals(scopeArgument)); + if (instance != (null, null)) + return (T) instance.instance; + + T createdInstance = CreateInstance(registration, arguments); + instances.Add((scopeArgument, createdInstance)); + + return createdInstance; + } + + T newInstance = CreateInstance(registration, arguments); + _multitons.Add((typeof(T), registration.Scope, new List<(object, object)>() {(scopeArgument, newInstance)})); + + return newInstance; + } + /// /// Creates an instance of a given type /// @@ -206,6 +247,7 @@ namespace LightweightIocContainer { _registrations.Clear(); _singletons.Clear(); + _multitons.Clear(); } } } \ No newline at end of file diff --git a/LightweightIocContainer/Lifestyle.cs b/LightweightIocContainer/Lifestyle.cs index 4582a92..d3dc7fc 100644 --- a/LightweightIocContainer/Lifestyle.cs +++ b/LightweightIocContainer/Lifestyle.cs @@ -19,8 +19,11 @@ namespace LightweightIocContainer /// /// One instance is created that gets returned every time an instance is resolved /// - Singleton + Singleton, - //TODO: Add Lifestyle.Multiton + /// + /// A new instance gets created if the given scope has no created instance yet. Otherwise the already created instance is used. + /// + Multiton } } \ No newline at end of file diff --git a/LightweightIocContainer/LightweightIocContainer.csproj b/LightweightIocContainer/LightweightIocContainer.csproj index b49c198..e4d8381 100644 --- a/LightweightIocContainer/LightweightIocContainer.csproj +++ b/LightweightIocContainer/LightweightIocContainer.csproj @@ -7,13 +7,12 @@ A lightweight IOC Container https://github.com/SimonG96/LightweightIocContainer https://github.com/SimonG96/LightweightIocContainer/blob/master/LICENSE + latest - - diff --git a/LightweightIocContainer/Registrations/MultitonRegistration.cs b/LightweightIocContainer/Registrations/MultitonRegistration.cs new file mode 100644 index 0000000..92e8694 --- /dev/null +++ b/LightweightIocContainer/Registrations/MultitonRegistration.cs @@ -0,0 +1,27 @@ +// // Author: Gockner, Simon +// // Created: 2019-06-07 +// // Copyright(c) 2019 SimonG. All Rights Reserved. + +using System; +using LightweightIocContainer.Interfaces.Registrations; + +namespace LightweightIocContainer.Registrations +{ + /// + /// The registration that is used to register a multiton + /// + /// The registered interface + public class MultitonRegistration : DefaultRegistration, IMultitonRegistration + { + public MultitonRegistration(Type interfaceType, Type implementationType, Type scope) + : base(interfaceType, implementationType, Lifestyle.Multiton) + { + Scope = scope; + } + + /// + /// The type of the multiton scope + /// + public Type Scope { get; } + } +} \ No newline at end of file diff --git a/LightweightIocContainer/Registrations/RegistrationFactory.cs b/LightweightIocContainer/Registrations/RegistrationFactory.cs index 1bb72a2..eefd5fd 100644 --- a/LightweightIocContainer/Registrations/RegistrationFactory.cs +++ b/LightweightIocContainer/Registrations/RegistrationFactory.cs @@ -24,6 +24,18 @@ namespace LightweightIocContainer.Registrations return new DefaultRegistration(typeof(TInterface), typeof(TImplementation), lifestyle); } + /// + /// Register an Interface with a Type that implements it as a multiton and create a + /// + /// The Interface to register + /// The Type that implements the + /// The Type of the multiton scope + /// A new created with the given parameters + public static IMultitonRegistration Register() where TImplementation : TInterface + { + return new MultitonRegistration(typeof(TInterface), typeof(TImplementation), typeof(TScope)); + } + /// /// Register an Interface as an abstract typed factory and create a /// diff --git a/Test.LightweightIocContainer/IocContainerTest.cs b/Test.LightweightIocContainer/IocContainerTest.cs index 3a08a71..7a6e99b 100644 --- a/Test.LightweightIocContainer/IocContainerTest.cs +++ b/Test.LightweightIocContainer/IocContainerTest.cs @@ -22,6 +22,12 @@ namespace Test.LightweightIocContainer { ITest Create(); ITest Create(string name); + ITest Create(MultitonScope scope); + } + + private interface ITestFactoryNoCreate + { + } private class Test : ITest @@ -37,6 +43,11 @@ namespace Test.LightweightIocContainer } } + public class MultitonScope + { + + } + #endregion @@ -70,12 +81,31 @@ namespace Test.LightweightIocContainer Assert.AreEqual(iocContainer, returnedContainer); } + [Test] + public void TestRegisterMultiple() + { + IIocContainer iocContainer = new IocContainer(); + + iocContainer.Register(RegistrationFactory.Register()); + var exception = Assert.Throws(() => iocContainer.Register(RegistrationFactory.Register())); + Assert.AreEqual(typeof(ITest), exception.Type); + } + + [Test] + public void RegisterFactoryWithoutCreate() + { + IIocContainer iocContainer = new IocContainer(); + + Assert.Throws(() => iocContainer.Register(RegistrationFactory.RegisterFactory(iocContainer))); + } + [Test] public void TestResolveNotRegistered() { IIocContainer iocContainer = new IocContainer(); - Assert.Throws(() => iocContainer.Resolve()); + var exception = Assert.Throws(() => iocContainer.Resolve()); + Assert.AreEqual(typeof(ITest), exception.Type); } [Test] @@ -135,6 +165,44 @@ namespace Test.LightweightIocContainer Assert.AreEqual(resolvedTest, secondResolvedTest); } + [Test] + public void TestResolveMultiton() + { + IIocContainer iocContainer = new IocContainer(); + iocContainer.Register(RegistrationFactory.Register()); + + MultitonScope scope1 = new MultitonScope(); + MultitonScope scope2 = new MultitonScope(); + + ITest resolvedTest1 = iocContainer.Resolve(scope1); + ITest resolvedTest2 = iocContainer.Resolve(scope1); + ITest resolvedTest3 = iocContainer.Resolve(scope2); + + Assert.AreSame(resolvedTest1, resolvedTest2); + Assert.AreNotSame(resolvedTest1, resolvedTest3); + Assert.AreNotSame(resolvedTest2, resolvedTest3); + } + + [Test] + public void TestResolveMultitonNoArgs() + { + IIocContainer iocContainer = new IocContainer(); + iocContainer.Register(RegistrationFactory.Register()); + + var exception = Assert.Throws(() => iocContainer.Resolve()); + Assert.AreEqual(typeof(ITest), exception.Type); + } + + [Test] + public void TestResolveMultitonWrongArgs() + { + IIocContainer iocContainer = new IocContainer(); + iocContainer.Register(RegistrationFactory.Register()); + + var exception = Assert.Throws(() => iocContainer.Resolve(new object())); + Assert.AreEqual(typeof(ITest), exception.Type); + } + [Test] public void TestResolveTransient() { @@ -185,5 +253,26 @@ namespace Test.LightweightIocContainer Assert.IsInstanceOf(createdTest); } + + [Test] + public void TestResolveMultitonFromFactory() + { + IIocContainer iocContainer = new IocContainer(); + iocContainer.Register(RegistrationFactory.Register()); + iocContainer.Register(RegistrationFactory.RegisterFactory(iocContainer)); + + MultitonScope scope1 = new MultitonScope(); + MultitonScope scope2 = new MultitonScope(); + + ITestFactory testFactory = iocContainer.Resolve(); + + ITest resolvedTest1 = testFactory.Create(scope1); + ITest resolvedTest2 = testFactory.Create(scope1); + ITest resolvedTest3 = testFactory.Create(scope2); + + Assert.AreSame(resolvedTest1, resolvedTest2); + Assert.AreNotSame(resolvedTest1, resolvedTest3); + Assert.AreNotSame(resolvedTest2, resolvedTest3); + } } } \ No newline at end of file