Last active
November 16, 2016 11:34
-
-
Save Dalstroem/273ce0f3804ccd725e3abc6507e0d44e to your computer and use it in GitHub Desktop.
Simple IoC container with dependency injection feature.
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.Concurrent; | |
using System.Collections.Generic; | |
using System.Linq; | |
namespace Dalstroem | |
{ | |
public class Container | |
{ | |
private readonly ConcurrentDictionary<Type, ContainerEntry> _entries = new ConcurrentDictionary<Type, ContainerEntry>(); | |
/// <summary> | |
/// Registers a pre-constructed instance against a type. This is useful | |
/// when an object requires a specific state. | |
/// </summary> | |
/// <param name="service"></param> | |
/// <param name="implementation"></param> | |
public void RegisterInstance(Type service, object implementation) | |
{ | |
if (implementation == null) throw new ArgumentNullException(nameof(implementation), @"The parameter cannot be null."); | |
RegisterHandler(service, (container, objects) => implementation); | |
} | |
/// <summary> | |
/// Registers an implementation against a type. A new instance of the | |
/// type is returned every time. | |
/// </summary> | |
/// <param name="implementation"></param> | |
public void RegisterPerRequest(Type implementation) | |
{ | |
RegisterPerRequest(implementation, implementation); | |
} | |
/// <summary> | |
/// Registers an implementation against a type. A new instance of the | |
/// type is returned every time. | |
/// </summary> | |
/// <param name="service"></param> | |
/// <param name="implementation"></param> | |
public void RegisterPerRequest(Type service, Type implementation) | |
{ | |
if (implementation == null) throw new ArgumentNullException(nameof(implementation), @"The parameter cannot be null."); | |
RegisterHandler(service, (container, objects) => container.BuildInstance(implementation, objects)); | |
} | |
/// <summary> | |
/// Registers an implementation against a type. The same instance of | |
/// the type is returned every time. The object is constructed when | |
/// first requested. | |
/// </summary> | |
/// <param name="service"></param> | |
/// <param name="implementation"></param> | |
public void RegisterSingleton(Type service, Type implementation) | |
{ | |
if (implementation == null) throw new ArgumentNullException(nameof(implementation), @"The parameter cannot be null."); | |
object singleton = null; | |
RegisterHandler(service, (container, objects) => singleton ?? (singleton = container.BuildInstance(implementation, objects))); | |
} | |
/// <summary> | |
/// Registers a handler against a type. This allows the factory method | |
/// to take advantage of the container itself which is useful in | |
/// complex construction scenarios. | |
/// </summary> | |
/// <param name="service"></param> | |
/// <param name="handler"></param> | |
public void RegisterHandler(Type service, Func<Container, object[], object> handler) | |
{ | |
if (handler == null) throw new ArgumentNullException(nameof(handler), @"The parameter cannot be null."); | |
var entry = _entries.GetOrAdd(service, type => new ContainerEntry()); | |
entry.Handler = handler; | |
} | |
/// <summary> | |
/// Unregisters a service. | |
/// </summary> | |
/// <param name="service"></param> | |
public void Unregister(Type service) | |
{ | |
if (service == null) throw new ArgumentNullException(nameof(service), @"The parameter cannot be null."); | |
ContainerEntry entry; | |
_entries.TryRemove(service, out entry); | |
} | |
/// <summary> | |
/// Gets an instance of a registered type. | |
/// </summary> | |
/// <param name="service"></param> | |
/// <param name="optional"></param> | |
/// <returns></returns> | |
public object GetInstance(Type service, params object[] optional) | |
{ | |
ContainerEntry entry; | |
if (_entries.TryGetValue(service, out entry)) | |
{ | |
return entry.Handler(this, optional); | |
} | |
return null; | |
} | |
/// <summary> | |
/// Builds an instance of the type provided. | |
/// </summary> | |
/// <param name="type"></param> | |
/// <param name="optional"></param> | |
/// <returns></returns> | |
private object BuildInstance(Type type, object[] optional) | |
{ | |
var args = DetermineConstructorArgs(type, optional); | |
return ActivateInstance(type, args); | |
} | |
/// <summary> | |
/// Returns an array of parameters of the type provided. | |
/// </summary> | |
/// <param name="type"></param> | |
/// <param name="optional"></param> | |
/// <returns></returns> | |
private object[] DetermineConstructorArgs(Type type, object[] optional) | |
{ | |
var constructors = from c in type.GetConstructors() | |
let parameters = c.GetParameters() | |
where c.IsPublic | |
orderby parameters.Length descending | |
select c; | |
var constructor = constructors.FirstOrDefault(); | |
var args = new List<object>(); | |
if (constructor != null) | |
{ | |
var startIndex = 0; | |
foreach (var parameter in constructor.GetParameters()) | |
{ | |
var parameterType = parameter.ParameterType; | |
var instance = GetInstance(parameterType); | |
var index = Array.FindIndex(optional, startIndex, o => parameterType.IsInstanceOfType(o)); | |
if (instance == null && index > -1) | |
{ | |
instance = optional[index]; | |
startIndex = index + 1; | |
} | |
args.Add(instance); | |
} | |
} | |
return args.ToArray(); | |
} | |
/// <summary> | |
/// Creates the instance of the type with the arguments. | |
/// </summary> | |
/// <param name="type"></param> | |
/// <param name="args"></param> | |
/// <returns></returns> | |
private object ActivateInstance(Type type, object[] args) | |
{ | |
return Activator.CreateInstance(type, args); | |
} | |
} | |
internal class ContainerEntry | |
{ | |
public Func<Container, object[], object> Handler { get; set; } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment