// 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();
}
}
}