Last active
December 21, 2015 08:38
-
-
Save adbre/6279455 to your computer and use it in GitHub Desktop.
Micro Inversion of Control Container
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
{ | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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