Skip to content

Instantly share code, notes, and snippets.

@bryanmenard
Created November 14, 2011 03:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bryanmenard/1363161 to your computer and use it in GitHub Desktop.
Save bryanmenard/1363161 to your computer and use it in GitHub Desktop.
dioc - Dependency injection and Inversion of control
// Release under MIT license Copyright (c) 2011 Bryan Menard, http://www.bryblog.com/license.txt
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Dioc
{
/// <summary>
/// Container for components. The container is not a performance superstar, but performs pretty well with
/// singleton components.
/// </summary>
/// <remarks>
/// This container is implemented from scratch to avoid having external dependencies to IoC containers like Castle.Windsor.
/// </remarks>
public class ComponentContainer
{
/// <summary>
/// All of the registered components
/// </summary>
private readonly Dictionary<Type, ComponentRegistration> Registrations = new Dictionary<Type, ComponentRegistration>();
/// <summary>
/// All of the registered and created singletons
/// </summary>
private readonly Dictionary<Type, object> Singletons = new Dictionary<Type, object>();
/// <summary>
/// Registers a singleton component, created at most once, when requested.
/// </summary>
/// <param name="parameters">The parameters to inject manually</param>
/// <typeparam name="T">The type of the component</typeparam>
public void Singleton<T>(Hashtable parameters = null)
{
Singleton<T, T>(parameters);
}
/// <summary>
/// Registers a singleton component, created at most once, when requested.
/// </summary>
/// <param name="parameters">The parameters to inject manually</param>
/// <typeparam name="TInterface">The type of the interface</typeparam>
/// <typeparam name="TImplementation">The type of the implementation</typeparam>
public void Singleton<TInterface, TImplementation>(Hashtable parameters = null) where TImplementation : TInterface
{
// Ensure the type was not already registered
EnsureNotRegistered(typeof (TInterface));
// Add a registration for that type
Registrations.Add(typeof (TInterface), ComponentRegistration.CreateSingleton(typeof (TImplementation), parameters));
}
/// <summary>
/// Registers a singleton component, created at most once when requested, using the provided factory method
/// </summary>
/// <param name="factoryMethod">The factory method to use in order to create the component</param>
/// <param name="parameters">The parameters to inject manually</param>
/// <typeparam name="T">The type of the component</typeparam>
public void Singleton<T>(Func<T> factoryMethod, Hashtable parameters = null)
{
// Ensure the type was not already registered
EnsureNotRegistered(typeof(T));
// Add a registration for that type
Registrations.Add(typeof(T), ComponentRegistration.CreateSingleton(typeof(T), () => factoryMethod(), parameters));
}
/// <summary>
/// Registers a transient component, to be created each time it is requested
/// </summary>
/// <param name="parameters">The parameters to inject manually</param>
/// <typeparam name="T">The type of the component</typeparam>
public void Transient<T>(Hashtable parameters = null)
{
Transient<T, T>(parameters);
}
/// <summary>
/// Registers a transient component, to be created each time it is requested
/// </summary>
/// <param name="parameters">The parameters to inject manually</param>
/// <typeparam name="TInterface">The type of the interface</typeparam>
/// <typeparam name="TImplementation">The type of the implementation</typeparam>
public void Transient<TInterface, TImplementation>(Hashtable parameters = null) where TImplementation : TInterface
{
// Ensure the type was not already registered
EnsureNotRegistered(typeof(TInterface));
// Add a registration for that type
Registrations.Add(typeof(TInterface), ComponentRegistration.CreateTransient(typeof(TImplementation), parameters));
}
/// <summary>
/// Registers a transient component, to be created each time it is requested using the provided factory method
/// </summary>
/// <param name="factoryMethod">The factory method to use in order to create the component</param>
/// <param name="parameters">The parameters to inject manually</param>
/// <typeparam name="T">The type of the component</typeparam>
public void Transient<T>(Func<T> factoryMethod, Hashtable parameters = null)
{
// Ensure the type was not already registered
EnsureNotRegistered(typeof(T));
// Add a registration for that type
Registrations.Add(typeof(T), ComponentRegistration.CreateTransient(typeof(T), () => factoryMethod(), parameters));
}
/// <summary>
/// Registers a component by specifying the instance to use
/// </summary>
/// <param name="instance">The instance to user</param>
/// <typeparam name="T">The instance of the component</typeparam>
public void Instance<T>(T instance)
{
Instance<T, T>(instance);
}
/// <summary>
/// Registers a component by specifying the instance to use
/// </summary>
/// <param name="instance">The instance to user</param>
/// <typeparam name="TInterface">The type of the interface</typeparam>
/// <typeparam name="TImplementation">The type of the implementation</typeparam>
public void Instance<TInterface, TImplementation>(TInterface instance) where TImplementation : TInterface
{
// Ensure the type was not already registered
EnsureNotRegistered(typeof(TInterface));
// Add a registration for that type
Registrations.Add(typeof(TInterface), ComponentRegistration.CreateInstance(typeof(TImplementation), instance));
}
/// <summary>
/// Ensure no component of the given type is registered yet
/// </summary>
/// <param name="type">The type of the component</param>
private void EnsureNotRegistered(Type type)
{
// Ensure the type was not already registered
if (Registrations.ContainsKey(type))
{
throw ComponentResolutionException.AlreadyRegistered(type);
}
}
/// <summary>
/// Get a component of the given type
/// </summary>
/// <param name="parameters">The parameters to inject manually</param>
/// <typeparam name="T">The type of the component</typeparam>
/// <returns>The component of the requested type</returns>
public T Get<T>(Hashtable parameters = null)
{
return (T)GetInternal(typeof(T), null, parameters);
}
/// <summary>
/// Get a component of the given type
/// </summary>
/// <param name="type">The type of the component</param>
/// <param name="parameters">The parameters to inject manually</param>
/// <returns>The component of the requested type</returns>
public object Get(Type type, Hashtable parameters = null)
{
return GetInternal(type, null, parameters);
}
/// <summary>
/// Internal method for getting a type by specifying and existing set of pending resolutions, mainly used
/// to detect circular dependencies
/// </summary>
/// <param name="type">The type of the component</param>
/// <param name="pendingResolutions">A set of pending resolutions</param>
/// <param name="parameters">The parameters to inject manually</param>
/// <returns>The component of the requested type</returns>
private object GetInternal(Type type, IDictionary<Type, ComponentRegistration> pendingResolutions, Hashtable parameters)
{
// Get the registration for the given type
ComponentRegistration registration;
// Ensure the component is registered
if (!Registrations.TryGetValue(type, out registration))
{
throw ComponentResolutionException.NotRegistered(type);
}
// Try and retrieve the instance as a singleton instance when appropriate
if (registration.Singleton)
{
object singleton;
if (Singletons.TryGetValue(type, out singleton))
return singleton;
}
object instance;
if (registration.Instance != null)
{
// Use the instance if provided
instance = registration.Instance;
}
else if (registration.FactoryMethod != null)
{
// Use the factory method provided
instance = registration.FactoryMethod();
}
else
{
// Find the constructor for the given type
var ctor = GetConstructor(registration.Type);
// Ensure there is an eligible constructor
if (ctor == null)
{
throw ComponentResolutionException.ConstructorNotFound(type);
}
var dependencies = ctor.GetParameters().ToList();
// Create a dictionary of seen type that are not yet resolved
pendingResolutions = pendingResolutions ?? new Dictionary<Type, ComponentRegistration>();
// Add an entry for the current type in the pending resolutions
pendingResolutions.Add(type, registration);
// The list of resolved dependencies
var resolvedDependencies = new object[dependencies.Count];
var currentDependencyIndex = 0;
// For every dependency
foreach (var dependency in dependencies)
{
// Try and get the parameter from the manual parameter list
if (parameters != null)
{
var argument = parameters[dependency.Name];
if (argument != null && dependency.ParameterType.IsAssignableFrom(argument.GetType()))
{
resolvedDependencies[currentDependencyIndex++] = argument;
continue;
}
}
// Try and get the parameter from the default parameter list
if (registration.Parameters != null)
{
var argument = registration.Parameters[dependency.Name];
if (argument != null && dependency.ParameterType.IsAssignableFrom(argument.GetType()))
{
resolvedDependencies[currentDependencyIndex++] = argument;
continue;
}
}
// The dependency could not be resolved with manually provided parameters.
// Resolve it using other components registered
var dependencyType = dependency.ParameterType;
// Ensure there is no circular dependency
if (pendingResolutions.ContainsKey(dependencyType))
{
throw ComponentResolutionException.CircularDependency(type, dependencyType);
}
try
{
// Create an instance of the dependency recursively
resolvedDependencies[currentDependencyIndex++] = GetInternal(dependencyType, pendingResolutions, null);
}
catch (ComponentResolutionException ex)
{
// Wrap an throw the exception
throw ComponentResolutionException.DependencyResolution(type, ex);
}
}
// Create an instance of the type using the constructor obtained before
instance = ctor.Invoke(resolvedDependencies);
}
// Register the instance as a singleton when appropriate
if (registration.Singleton)
Singletons.Add(type, instance);
// Return the newly created instance
return instance;
}
/// <summary>
/// Gets the constructor to use for insantiation
/// </summary>
/// <param name="type">The type for which to obtain a constructor</param>
/// <returns>The constructor to use for instantiation or null if no candidates exist</returns>
private static ConstructorInfo GetConstructor(Type type)
{
ConstructorInfo ctor = null;
int nbParams = -1;
// Find the constructor with the most parameters
foreach (var constructor in type.GetConstructors())
{
var nbParamsCurrent = constructor.GetParameters().Length;
if (nbParamsCurrent > nbParams)
{
ctor = constructor;
nbParams = nbParamsCurrent;
}
}
return ctor;
}
}
}
// Release under MIT license Copyright (c) 2011 Bryan Menard, http://www.bryblog.com/license.txt
using System;
using System.Collections;
using NUnit.Framework;
namespace Dioc
{
[TestFixture]
public class ComponentContainerTests
{
private ComponentContainer Container;
[SetUp]
public void SetUp()
{
Container = new ComponentContainer();
}
#region Component structure
// Components are defined below
// A
// B -> (A)
// C -> (A, A)
// D -> (B, C)
// E -> (E) Circular dependency
// F -> (G)
// G -> (H)
// H -> (F) Circular dependency
#endregion
[Test]
public void Get_SimpleTransient_ReturnsInstance()
{
Container.Transient<IA, A>();
var instance = Container.Get<IA>();
Assert.That(instance, Is.TypeOf<A>());
}
[Test]
public void Get_SameTransientTwice_ReturnsDifferentInstances()
{
Container.Transient<IA, A>();
var instance1 = Container.Get<IA>();
var instance2 = Container.Get<IA>();
Assert.That(instance1, Is.Not.SameAs(instance2));
}
[Test]
public void Get_SimpleSingleton_ReturnsInstance()
{
Container.Singleton<IA, A>();
var instance = Container.Get<IA>();
Assert.That(instance, Is.TypeOf<A>());
}
[Test]
public void Get_SameSingletonTwice_ReturnsSameInstance()
{
Container.Singleton<IA, A>();
var instance1 = Container.Get<IA>();
var instance2 = Container.Get<IA>();
Assert.That(instance1, Is.SameAs(instance2));
}
[Test]
public void Get_TransientWithTransientDependencies_ReturnsInstance()
{
Container.Transient<IA, A>();
Container.Transient<IB, B>();
var instance = Container.Get<IB>();
Assert.That(instance, Is.TypeOf<B>());
}
[Test]
public void Get_TransientWithSingletonDependencies_ReturnsSameDependencies()
{
Container.Singleton<IA, A>();
Container.Transient<IB, B>();
var instance1 = Container.Get<IB>();
var instance2 = Container.Get<IB>();
Assert.That(instance1, Is.Not.SameAs(instance2));
Assert.That(((B)instance1).A, Is.SameAs(((B)instance2).A));
}
[Test]
public void Get_SingletonWithTransientDependencies_ReturnsSameInstanceThusSameDependencies()
{
Container.Transient<IA, A>();
Container.Singleton<IB, B>();
var instance1 = Container.Get<IB>();
var instance2 = Container.Get<IB>();
Assert.That(instance1, Is.SameAs(instance2));
Assert.That(( (B)instance1 ).A, Is.SameAs(( (B)instance2 ).A));
}
[Test]
public void Get_Unregistered_ThrowException()
{
AssertThrowsWithRootCause(() => Container.Get<IF>(), ComponentResolutionError.NotRegistered);
}
[Test]
public void Get_TransientWithCircularDependency_ThrowsException()
{
Container.Singleton<IE, E>();
AssertThrowsWithRootCause(() => Container.Get<IE>(), ComponentResolutionError.CircularDependency);
}
[Test]
public void Get_TransientWithLargeCircularDependency_ThrowsException()
{
Container.Singleton<IF, F>();
Container.Singleton<IG, G>();
Container.Singleton<IH, H>();
AssertThrowsWithRootCause(() => Container.Get<IF>(), ComponentResolutionError.CircularDependency);
}
[Test]
public void Get_Instance_ReturnsInstance()
{
var expected = new A();
Container.Instance<IA, A>(expected);
var actual = Container.Get<IA>();
Assert.That(actual, Is.SameAs(expected));
}
[Test]
public void Get_TransientWithFactoryMethod_InvokesFactoryMethodEachTime()
{
var invocations = 0;
var expected = new A();
Container.Transient<IA>(() => { invocations++; return expected; });
var actual1 = Container.Get<IA>();
var actual2 = Container.Get<IA>();
Assert.That(invocations, Is.EqualTo(2));
Assert.That(actual1, Is.SameAs(expected));
Assert.That(actual2, Is.SameAs(expected));
}
[Test]
public void Get_TransientWithFactoryMethod_InvokesFactoryMethodLazily()
{
var invocations = 0;
var expected = new A();
Container.Transient<IA>(() => { invocations++; return expected; });
Assert.That(invocations, Is.EqualTo(0));
}
[Test]
public void Get_SingletonWithFactoryMethod_InvokesFactoryMethodOnce()
{
var invocations = 0;
var expected = new A();
Container.Singleton<IA>(() => { invocations++; return expected; });
var actual1 = Container.Get<IA>();
var actual2 = Container.Get<IA>();
Assert.That(invocations, Is.EqualTo(1));
Assert.That(actual1, Is.SameAs(expected));
Assert.That(actual2, Is.SameAs(expected));
}
[Test]
public void Get_SingletonWithFactoryMethod_InvokesFactoryMethodLazily()
{
var invocations = 0;
var expected = new A();
Container.Singleton<IA>(() => { invocations++; return expected; });
Assert.That(invocations, Is.EqualTo(0));
}
[Test]
public void Get_WithDefaultParametersForUnregisteredComponent_UsesProvidedParameters()
{
// Arrange
Container.Transient<IA, A>();
var parameters = new Hashtable { { "manual", "Injected!" } };
Container.Transient<IJ, J>(parameters);
// Act
var actual = Container.Get<IJ>();
// Assert
Assert.That(actual, Is.TypeOf<J>());
Assert.That(( (J)actual ).Manual, Is.EqualTo("Injected!"));
}
[Test]
public void Get_WithDefaultParametersForRegisteredComponent_UsesProvidedParameters()
{
// Arrange
Container.Transient<IA, A>();
var a = new A();
var parameters = new Hashtable { { "a", a }, { "manual", "Injected!" } };
Container.Transient<IJ, J>(parameters);
// Act
var actual = Container.Get<IJ>();
// Assert
Assert.That(actual, Is.TypeOf<J>());
Assert.That(( (J)actual ).A, Is.SameAs(a));
}
[Test]
public void Get_WithManualParametersForUnregisteredComponent_UsesProvidedParameters()
{
// Arrange
Container.Transient<IA, A>();
Container.Transient<IJ, J>();
// Act
var actual = Container.Get<IJ>(new Hashtable { { "manual", "Injected!" } });
// Assert
Assert.That(actual, Is.TypeOf<J>());
Assert.That(((J)actual).Manual, Is.EqualTo("Injected!"));
}
[Test]
public void Get_WithManualParametersForRegisteredComponent_UsesProvidedParameters()
{
// Arrange
Container.Transient<IA, A>();
var a = new A();
Container.Transient<IJ, J>();
// Act
var actual = Container.Get<IJ>(new Hashtable { { "a", a }, { "manual", "Injected!" } });
// Assert
Assert.That(actual, Is.TypeOf<J>());
Assert.That(( (J)actual ).A, Is.SameAs(a));
}
[Test]
public void Get_WithManualAndDefaultParameters_ManuelOverridesDefault()
{
// Arrange
Container.Transient<IA, A>();
var wrongA = new A();
var a = new A();
Container.Transient<IJ, J>(new Hashtable { { "a", wrongA }, { "manual", "Injected!" } });
// Act
var actual = Container.Get<IJ>(new Hashtable { { "a", a } });
// Assert
Assert.That(actual, Is.TypeOf<J>());
Assert.That(( (J)actual ).A, Is.SameAs(a));
}
private static void AssertThrowsWithRootCause(Action action, ComponentResolutionError rootCause)
{
Assert.That(() => action(),
Throws.InstanceOf<ComponentResolutionException>()
.With.Property("RootCause").EqualTo(rootCause));
}
}
#region Components
public class A : IA { }
public interface IA { }
public interface IB { }
public class B : IB
{
public B(IA a) { A = a; }
public IA A { get; private set; }
}
public interface IC { }
public class C : IC
{
public C(IA a1, IA a2) { A1 = a1; A2 = a2; }
public IA A1 { get; private set; }
public IA A2 { get; private set; }
}
public interface ID { }
public class D : ID
{
public D(IB b, IC c) { B = b; C = c; }
public IB B { get; private set; }
public IC C { get; private set; }
}
public interface IE { }
public class E : IE { public E(IE e) { } }
public interface IF { }
public class F : IF { public F(IG g) { } }
public interface IG { }
public class G : IG { public G(IH h) { } }
public interface IH { }
public class H : IH { public H(IF f) { } }
public interface IJ { }
public class J : IJ
{
public J(IA a, string manual) { A = a; Manual = manual; }
public IA A { get; private set; }
public string Manual { get; private set; }
}
#endregion
}
// Release under MIT license Copyright (c) 2011 Bryan Menard, http://www.bryblog.com/license.txt
using System;
using System.Collections;
using System.Diagnostics;
namespace Dioc
{
/// <summary>
/// Represents a registration for a component
/// </summary>
public class ComponentRegistration
{
/// <summary>
/// Gets the type of the component registered
/// </summary>
public Type Type { get; private set; }
/// <summary>
/// Gets whether the component is a singleton or not
/// </summary>
public bool Singleton { get; private set; }
/// <summary>
/// Gets the instance
/// </summary>
public object Instance { get; private set; }
/// <summary>
/// Gets the factory method to use in order to create the instance
/// </summary>
public Func<object> FactoryMethod { get; private set; }
/// <summary>
/// Gets the parameters to inject manually
/// </summary>
public Hashtable Parameters { get; private set; }
/// <summary>
/// Private constructor.
/// </summary>
private ComponentRegistration() { }
/// <summary>
/// Creates registration for a singleton instance, created only once using dependency injection
/// </summary>
/// <param name="type">The type registered</param>
/// <param name="parameters">The parameters to inject manually</param>
/// <returns>The registration</returns>
public static ComponentRegistration CreateSingleton(Type type, Hashtable parameters = null)
{
return new ComponentRegistration { Type = type, Singleton = true, Parameters = parameters };
}
/// <summary>
/// Creates a registration for a singleton instance, created using the provided factory method
/// </summary>
/// <param name="type">The type registered</param>
/// <param name="factoryMethod">The factory method used to create instances</param>
/// <param name="parameters">The parameters to inject manually</param>
/// <returns>The registration</returns>
public static ComponentRegistration CreateSingleton(Type type, Func<object> factoryMethod, Hashtable parameters = null)
{
return new ComponentRegistration { Type = type, FactoryMethod = factoryMethod, Singleton = true, Parameters = parameters };
}
/// <summary>
/// Creates a registration for transient instances, created every time its needed using dependency injection
/// </summary>
/// <param name="type">The type registered</param>
/// <param name="parameters">The parameters to inject manually</param>
/// <returns>The registration</returns>
public static ComponentRegistration CreateTransient(Type type, Hashtable parameters = null)
{
return new ComponentRegistration { Type = type, Parameters = parameters };
}
/// <summary>
/// Creates a registration for transient instances, created using the provided factory method
/// </summary>
/// <param name="type">The type registered</param>
/// <param name="factoryMethod">The factory method used to create instances</param>
/// <param name="parameters">The parameters to inject manually</param>
/// <returns>The registration</returns>
public static ComponentRegistration CreateTransient(Type type, Func<object> factoryMethod, Hashtable parameters = null)
{
return new ComponentRegistration { Type = type, FactoryMethod = factoryMethod, Parameters = parameters };
}
/// <summary>
/// Creates a registration for a instance, provided before-hand
/// </summary>
/// <param name="type">The type registered</param>
/// <param name="instance">The instance</param>
/// <returns>The registration</returns>
public static ComponentRegistration CreateInstance(Type type, object instance)
{
Debug.Assert(type.IsAssignableFrom(instance.GetType()));
return new ComponentRegistration { Type = type, Instance = instance };
}
}
}
// Release under MIT license Copyright (c) 2011 Bryan Menard, http://www.bryblog.com/license.txt
using System;
using System.Text;
namespace Dioc
{
public enum ComponentResolutionError
{
None = 0,
AlreadyRegistered,
NotRegistered,
ConstructorNotFound,
DependencyResolution,
CircularDependency
}
/// <summary>
/// Represents an exception with component resolution.
/// </summary>
[Serializable]
public class ComponentResolutionException : ApplicationException
{
public ComponentResolutionError Error { get; private set; }
public ComponentResolutionError RootCause { get; private set; }
public ComponentResolutionException(string message, ComponentResolutionError error)
: base(message)
{
Error = RootCause = error;
}
public ComponentResolutionException(string message, Exception inner, ComponentResolutionError error)
: base(FormatMessage(message, inner), inner)
{
Error = error;
var componentException = inner as ComponentResolutionException;
if (componentException != null)
{
RootCause = componentException.RootCause;
}
}
private static string FormatMessage(string message, Exception inner)
{
return FormatMessage(message, inner.Message ?? string.Empty);
}
public static string FormatMessage(string message, string innerMessage)
{
var sb = new StringBuilder();
sb.AppendLine(message);
sb.Append((innerMessage ?? string.Empty).Replace(Environment.NewLine, Environment.NewLine + ".. "));
return sb.ToString();
}
public static ComponentResolutionException NotRegistered(Type type)
{
throw new ComponentResolutionException(
string.Format("Unable to resolve type {0}. The component was not registered.", type),
ComponentResolutionError.NotRegistered);
}
public static ComponentResolutionException AlreadyRegistered(Type type)
{
throw new ComponentResolutionException(
string.Format("The type {0} was already registered.", type),
ComponentResolutionError.AlreadyRegistered);
}
public static ComponentResolutionException ConstructorNotFound(Type type)
{
throw new ComponentResolutionException(
string.Format("Unable to find a suitable constructor for type {0}.", type),
ComponentResolutionError.ConstructorNotFound);
}
public static ComponentResolutionException DependencyResolution(Type type, Exception innerException)
{
throw new ComponentResolutionException(
string.Format("Unable to resolve dependencies for {0}.", type),
innerException,
ComponentResolutionError.DependencyResolution);
}
public static ComponentResolutionException CircularDependency(Type type, Type dependency)
{
return new ComponentResolutionException(
FormatMessage(string.Format("A circular dependency was detected within {0}.", type), dependency.ToString()),
ComponentResolutionError.CircularDependency);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment