Skip to content

Instantly share code, notes, and snippets.

@adbre
Last active December 21, 2015 08:38
Show Gist options
  • Save adbre/6279455 to your computer and use it in GitHub Desktop.
Save adbre/6279455 to your computer and use it in GitHub Desktop.
Micro Inversion of Control Container
using System;
using System.Collections.Generic;
using System.Linq;
namespace MicroIoC
{
public interface IMicroIoContainer
{
object Resolve(Type type);
T Resolve<T>(Type type) where T : class;
T Resolve<T>() where T : class;
void Register<T>() where T : class;
void RegisterInstance<T>() where T : class;
void RegisterInstance<T>(T impl) where T : class;
void RegisterInstance<T, TImpl>()
where T : class
where TImpl : class, T;
void Register<T, TImpl>()
where T : class
where TImpl : class;
void Register<T, TImpl>(TImpl impl)
where T : class
where TImpl : class;
}
public class MicroIoContainer : IMicroIoContainer
{
private readonly Dictionary<Type, object> _instances = new Dictionary<Type, object>();
private readonly Dictionary<Type, Type> _types = new Dictionary<Type, Type>();
public MicroIoContainer()
{
RegisterInstance(this);
}
public object Resolve(Type type)
{
return InnerResolve(type);
}
public T Resolve<T>(Type type) where T : class
{
if (type == null) throw new ArgumentNullException("type");
if (!typeof(T).IsAssignableFrom(type)) throw new Exception(string.Format("{0} is not assignable from {1}", typeof(T), type));
return (T)InnerResolve(type);
}
public T Resolve<T>() where T : class
{
return (T)InnerResolve(typeof(T));
}
public void Register<T>() where T : class
{
Register<T,T>();
}
public void RegisterInstance<T>(T impl) where T : class
{
Register<T,T>(impl);
}
public void RegisterInstance<T>() where T : class
{
ThrowIfTIsNotValidImplementation<T, T>();
RegisterInstance<T>(Resolve<T>());
}
public void RegisterInstance<T, TImpl>()
where T : class
where TImpl : class, T
{
ThrowIfTIsNotValidImplementation<T, TImpl>();
RegisterInstance<T>(Resolve<TImpl>());
}
public void Register<T, TImpl>()
where T : class
where TImpl : class
{
ThrowIfTIsNotValidImplementation<T,TImpl>();
_types.Add(typeof(T), typeof(TImpl));
}
public void Register<T, TImpl>(TImpl impl)
where T : class
where TImpl : class
{
if (impl == null) throw new ArgumentNullException("impl");
if (!typeof(T).IsAssignableFrom(typeof(TImpl))) throw new ArgumentException("T must be assignable from TImpl");
_instances.Add(typeof(T), impl);
}
private void ThrowIfTIsNotValidImplementation<T,TImpl>()
{
var timpl = typeof(TImpl);
var t = typeof(T);
if (!t.IsAssignableFrom(timpl)) throw new ArgumentException(string.Format("{0} is not assignable from {1}", t, timpl));
if (timpl.IsInterface) throw new ArgumentException(string.Format("Cannot register {0} as an implementation of {1}, since it is an interface.", t, timpl));
if (timpl.IsAbstract) throw new ArgumentException(string.Format("Cannot register {0} as an implementation of {1}, since it is abstract.", t, timpl));
}
// ReSharper disable UnusedParameter.Local
private object InnerResolve(Type type, bool throwError = true)
// ReSharper restore UnusedParameter.Local
{
if (type == null) throw new ArgumentNullException("type");
var errorMessage = string.Format("Could not resolve {0}", type);
object result;
try
{
result = GetActiveInstance(type) ?? Activate(type);
if (throwError && result == null) throw new Exception();
}
catch (Exception error)
{
throw new Exception(errorMessage, error);
}
if (result == null)
throw new Exception(errorMessage);
return result;
}
private object GetActiveInstance(Type type)
{
object obj;
return _instances.TryGetValue(type, out obj) ? obj : null;
}
private object Activate(Type type)
{
var activators = new Func<Type, object>[]
{
ActivateFromExplicitTypeDefinition,
ActivateByAutoDiscoverConstructorWithDependencyParameters,
ActivateDefaultConstructor,
ActivateByFindingSubClassOfT
};
foreach (var activator in activators)
{
var obj = activator(type);
if (obj == null) continue;
ResolvePropertyDependencies(obj);
return obj;
}
return null;
}
private object ActivateByFindingSubClassOfT(Type type)
{
foreach (var objType in _instances.Keys)
{
if (type.IsAssignableFrom(objType)) return InnerResolve(objType, false);
}
foreach (var map in _types)
{
if (type.IsAssignableFrom(map.Key)) return InnerResolve(map.Value, true);
}
return null;
}
private object ActivateDefaultConstructor(Type type)
{
if (Type.GetTypeCode(type) != TypeCode.Object) return null;
var defaultConstructor = type.GetConstructor(new Type[0]);
if (defaultConstructor != null)
return defaultConstructor.Invoke(null);
return null;
}
private object ActivateFromExplicitTypeDefinition(Type type)
{
Type implType;
if (!_types.TryGetValue(type, out implType)) return null;
return ActivateDefaultConstructor(implType);
}
private object ActivateByAutoDiscoverConstructorWithDependencyParameters(Type type)
{
foreach (var constructor in type.GetConstructors().OrderByDescending(c => c.GetParameters().Length))
{
var parameters = constructor
.GetParameters()
.Select(i => InnerResolve(i.ParameterType, false))
.ToArray();
if (parameters.Any(p => p == null)) continue;
return constructor.Invoke(parameters);
}
return null;
}
private void ResolvePropertyDependencies(object obj)
{
foreach (var property in obj.GetType().GetProperties())
{
if (!property.CanWrite) continue;
if (!property.CanRead) continue;
if (property.GetValue(obj) != null) continue;
var typeCode = Type.GetTypeCode(property.PropertyType);
if (typeCode != TypeCode.Object) continue;
if (!property.GetCustomAttributes(typeof (DependencyAttribute), true).Any()) continue;
var dependency = InnerResolve(property.PropertyType, false);
if (dependency == null) continue;
property.SetValue(obj, dependency);
}
}
}
[AttributeUsage(AttributeTargets.Property)]
public class DependencyAttribute : Attribute
{
}
}
using System;
using NUnit.Framework;
namespace MicroIoC.Tests
{
[TestFixture]
public class MicroIoContainerTests
{
[Test]
public void ShouldRecursivlyResolveTypes()
{
var sut = new MicroIoContainer();
sut.Register<FirstDependencyClass>();
sut.Register<SecondDependencyClass>();
var actual = sut.Resolve<MyClass>();
Assert.That(actual.FirstDependency, Is.Not.Null);
Assert.That(actual.SecondDependency, Is.Not.Null);
}
[Test]
public void ShouldResolveFromTypeInstance()
{
var sut = new MicroIoContainer();
var actual = sut.Resolve(typeof(SecondDependencyClass));
Assert.That(actual, Is.Not.Null);
}
[Test]
public void ShouldResolveInstances()
{
var expectedFirstDependency = new FirstDependencyClass();
var expectedSecondDependency = new SecondDependencyClass();
var sut = new MicroIoContainer();
sut.RegisterInstance<FirstDependencyClass>(expectedFirstDependency);
sut.RegisterInstance<SecondDependencyClass>(expectedSecondDependency);
var actual = sut.Resolve<MyClass>();
Assert.That(actual.FirstDependency, Is.SameAs(expectedFirstDependency));
Assert.That(actual.SecondDependency, Is.SameAs(expectedSecondDependency));
}
[Test]
public void ShouldResolveSubClass()
{
var sut = new MicroIoContainer();
sut.Register<ISecondDependency,DuplicateSecondDependencyClass>();
var actual = sut.Resolve<MyClass>();
Assert.That(actual.SecondDependency, Is.InstanceOf<DuplicateSecondDependencyClass>());
}
[Test]
public void ShouldResolveRegisteredInstance()
{
var expectedFirstDependency = new FirstDependencyClass();
var expectedSecondDependency = new SecondDependencyClass();
var expected = new MyClass(expectedFirstDependency) {SecondDependency = expectedSecondDependency};
var sut = new MicroIoContainer();
sut.RegisterInstance(expected);
var actual = sut.Resolve<MyClass>();
Assert.That(actual, Is.SameAs(expected));
Assert.That(actual.FirstDependency, Is.SameAs(expectedFirstDependency));
Assert.That(actual.SecondDependency, Is.SameAs(expectedSecondDependency));
}
[Test]
public void ShouldResolveDefaultConstructor()
{
var sut = new MicroIoContainer();
var actual = sut.Resolve<FirstDependencyClass>();
Assert.That(actual, Is.Not.Null);
}
[Test]
public void ShouldResolveSubClassOfT()
{
// Arrange
var sut = new MicroIoContainer();
var expected = new FirstDependencyClass();
sut.RegisterInstance(expected);
// Act
var actual = sut.Resolve<IFirstDependency>();
// Assert
Assert.That(actual, Is.SameAs(expected));
}
[Test]
public void ShouldResolveExplicitType()
{
var sut = new MicroIoContainer();
sut.Register<ISecondDependency, DuplicateSecondDependencyClass>();
var actual = sut.Resolve<ISecondDependency>();
Assert.That(actual, Is.InstanceOf<DuplicateSecondDependencyClass>());
}
[Test]
public void ShouldResolveSelf()
{
var sut = new MicroIoContainer();
var actual = sut.Resolve<IMicroIoContainer>();
Assert.That(actual, Is.SameAs(sut));
}
[Test]
public void StackOverflowException_ShouldNotRecursivilyCallItself()
{
var sut = new MicroIoContainer();
sut.Register<ISecondDependency, SecondDependencyClass>();
sut.Register<IThirdDependency, ThirdDependencyClass>();
var actual = sut.Resolve<IThirdDependency>();
Assert.That(actual, Is.InstanceOf<ThirdDependencyClass>());
Assert.That(actual.SecondDependency, Is.InstanceOf<SecondDependencyClass>());
}
[Test]
public void ShouldThrowArgumentExceptionIfTIsNotAssignableFromTImpl()
{
var sut = new MicroIoContainer();
Assert.Throws<ArgumentException>(() => sut.Register<IFirstDependency, SecondDependencyClass>());
}
[Test]
public void ShouldThrowArgumentExceptionIfTImplisInterface()
{
var sut = new MicroIoContainer();
Assert.Throws<ArgumentException>(() => sut.Register<IFirstDependency, IFirstDependency>());
}
[Test]
public void ShouldThrowArgumentExceptionIfTImplIsAbstract()
{
var sut = new MicroIoContainer();
Assert.Throws<ArgumentException>(() => sut.Register<IFirstDependency, FirstAbstractClass>());
}
[Test]
public void ShouldThrowArgumentExceptionIfTImplIsAbstractWhenRegisterInstanceImpl()
{
var sut = new MicroIoContainer();
Assert.Throws<ArgumentException>(() => sut.RegisterInstance<IFirstDependency, FirstAbstractClass>());
}
[Test]
public void ShouldThrowArgumentExceptionIfTImplIsAbstractWhenRegisterInstance()
{
var sut = new MicroIoContainer();
Assert.Throws<ArgumentException>(() => sut.RegisterInstance<FirstAbstractClass>());
}
[Test]
public void ShouldNotResolvePropertiesMissingTheDependencyAttribute()
{
var sut = new MicroIoContainer();
sut.Register<IFirstDependency, FirstDependencyClass>();
sut.Register<ISecondDependency, SecondDependencyClass>();
var actual = sut.Resolve<PropertyDependencyTestClass>();
Assert.That(actual.FirstDependency, Is.InstanceOf<FirstDependencyClass>(), "#1");
Assert.That(actual.SecondDependency, Is.Null, "#2");
}
private class MyClass
{
public FirstDependencyClass FirstDependency { get; set; }
[Dependency]
public ISecondDependency SecondDependency { get; set; }
public MyClass(FirstDependencyClass firstDependency)
{
FirstDependency = firstDependency;
}
}
private interface IFirstDependency {}
private interface ISecondDependency {}
private class FirstDependencyClass : IFirstDependency {}
private class SecondDependencyClass : ISecondDependency {}
private class DuplicateSecondDependencyClass : ISecondDependency {}
private interface IThirdDependency
{
ISecondDependency SecondDependency { get; }
}
private class ThirdDependencyClass : IThirdDependency
{
private readonly ISecondDependency _secondDependency;
public ThirdDependencyClass(ISecondDependency dependency)
{
_secondDependency = dependency;
}
public ISecondDependency SecondDependency
{
get { return _secondDependency; }
}
}
private class PropertyDependencyTestClass
{
[Dependency]
public IFirstDependency FirstDependency { get; set; }
public ISecondDependency SecondDependency { get; set; }
}
private abstract class FirstAbstractClass : IFirstDependency {}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment