diff --git a/LightweightIocContainer/Exceptions/CircularDependencyException.cs b/LightweightIocContainer/Exceptions/CircularDependencyException.cs
new file mode 100644
index 0000000..213d977
--- /dev/null
+++ b/LightweightIocContainer/Exceptions/CircularDependencyException.cs
@@ -0,0 +1,63 @@
+// Author: Gockner, Simon
+// Created: 2019-11-05
+// Copyright(c) 2019 SimonG. All Rights Reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using LightweightIocContainer.Interfaces;
+
+namespace LightweightIocContainer.Exceptions
+{
+ ///
+ /// A circular dependency was detected during
+ ///
+ internal class CircularDependencyException : Exception
+ {
+ ///
+ /// A circular dependency was detected during
+ ///
+ /// The currently resolving
+ /// The resolve stack at the time the was thrown
+ public CircularDependencyException(Type resolvingType, List resolveStack)
+ {
+ ResolvingType = resolvingType;
+ ResolveStack = resolveStack;
+ }
+
+
+ ///
+ /// The currently resolving
+ ///
+ public Type ResolvingType { get; }
+
+ ///
+ /// The resolve stack at the time the was thrown
+ ///
+ public List ResolveStack { get; }
+
+ ///
+ /// The exception message
+ ///
+ public override string Message
+ {
+ get
+ {
+ string message = $"Circular dependency has been detected when trying to resolve `{ResolvingType}`.\n" +
+ "Resolve stack that resulted in the circular dependency:\n" +
+ $"\t`{ResolvingType}` resolved as dependency of\n";
+
+ if (ResolveStack == null || !ResolveStack.Any())
+ return message;
+
+ for (int i = ResolveStack.Count - 1; i >= 1 ; i--)
+ {
+ message += $"\t`{ResolveStack[i]}` resolved as dependency of\n";
+ }
+
+ message += $"\t`{ResolveStack[0]}` which is the root type being resolved.";
+ return message;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/LightweightIocContainer/Interfaces/IIocContainer.cs b/LightweightIocContainer/Interfaces/IIocContainer.cs
index 18a7972..afa8af9 100644
--- a/LightweightIocContainer/Interfaces/IIocContainer.cs
+++ b/LightweightIocContainer/Interfaces/IIocContainer.cs
@@ -77,15 +77,6 @@ namespace LightweightIocContainer.Interfaces
/// An instance of the given
T Resolve(params object[] arguments);
- ///
- /// Gets an instance of the given
- ///
- /// The given
- /// The constructor arguments
- /// An instance of the given
- /// Could not find function
- object Resolve(Type type, object[] arguments);
-
///
/// Clear the multiton instances of the given from the registered multitons list
///
diff --git a/LightweightIocContainer/IocContainer.cs b/LightweightIocContainer/IocContainer.cs
index 9ceee68..f828caf 100644
--- a/LightweightIocContainer/IocContainer.cs
+++ b/LightweightIocContainer/IocContainer.cs
@@ -163,9 +163,10 @@ namespace LightweightIocContainer
///
/// The given
/// The constructor arguments
+ /// The current resolve stack
/// An instance of the given
/// Could not find function
- public object Resolve(Type type, object[] arguments)
+ 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);
@@ -173,7 +174,14 @@ namespace LightweightIocContainer
if (genericResolveMethod == null)
throw new InternalResolveException($"Could not find function {nameof(ResolveInternal)}");
- return genericResolveMethod.Invoke(this, new object[] {arguments});
+ 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();
+ }
}
///
@@ -181,15 +189,24 @@ namespace LightweightIocContainer
///
/// 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(params object[] arguments)
+ private T ResolveInternal(object[] arguments, List resolveStack = null)
{
IRegistrationBase registration = _registrations.FirstOrDefault(r => r.InterfaceType == typeof(T));
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
+
if (registration is IUnitTestCallbackRegistration unitTestCallbackRegistration)
{
return unitTestCallbackRegistration.UnitTestResolveCallback.Invoke(arguments);
@@ -197,11 +214,11 @@ namespace LightweightIocContainer
else if (registration is IDefaultRegistration defaultRegistration)
{
if (defaultRegistration.Lifestyle == Lifestyle.Singleton)
- return GetOrCreateSingletonInstance(defaultRegistration, arguments);
+ return GetOrCreateSingletonInstance(defaultRegistration, arguments, resolveStack);
else if (defaultRegistration is IMultitonRegistration multitonRegistration && defaultRegistration.Lifestyle == Lifestyle.Multiton)
- return GetOrCreateMultitonInstance(multitonRegistration, arguments);
+ return GetOrCreateMultitonInstance(multitonRegistration, arguments, resolveStack);
- return CreateInstance(defaultRegistration, arguments);
+ return CreateInstance(defaultRegistration, arguments, resolveStack);
}
else if (registration is ITypedFactoryRegistration typedFactoryRegistration)
{
@@ -217,8 +234,9 @@ namespace LightweightIocContainer
/// 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(IDefaultRegistration registration, params object[] arguments)
+ private T GetOrCreateSingletonInstance(IDefaultRegistration registration, object[] arguments, List resolveStack)
{
//if a singleton instance exists return it
object instance = _singletons.FirstOrDefault(s => s.type == typeof(T)).instance;
@@ -226,7 +244,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);
+ T newInstance = CreateInstance(registration, arguments, resolveStack);
_singletons.Add((typeof(T), newInstance));
return newInstance;
@@ -238,10 +256,11 @@ namespace LightweightIocContainer
/// 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, params object[] arguments)
+ 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));
@@ -257,13 +276,13 @@ namespace LightweightIocContainer
if (instances.TryGetValue(scopeArgument, out object instance))
return (T) instance;
- T createdInstance = CreateInstance(registration, arguments);
+ T createdInstance = CreateInstance(registration, arguments, resolveStack);
instances.Add(scopeArgument, createdInstance);
return createdInstance;
}
- T newInstance = CreateInstance(registration, arguments);
+ T newInstance = CreateInstance(registration, arguments, resolveStack);
ConditionalWeakTable