Last active
August 29, 2015 14:05
-
-
Save vkobel/48bf6b470346cf590e92 to your computer and use it in GitHub Desktop.
Simple Dependency Injector (contructors only) in .NET C#
This file contains hidden or 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 KobInjector { | |
/// <summary> | |
/// Simple Exeption to be thrown if a paramter cannot be resolved, it displays the whole injection stack | |
/// </summary> | |
public class InjectionStackException : Exception { | |
public InjectionStackException(Stack<Type> injectionStack) | |
: base(string.Format("Injection is not supported for stack: '{0}'", | |
string.Join(" <- ", injectionStack.Select(i => i.Name)))) { | |
} | |
} | |
/// <summary> | |
/// Static class that hold the dependency injection operations as well as the specified mappings | |
/// </summary> | |
public static class KobInjector { | |
private static Dictionary<Type, List<Type>> mappings = new Dictionary<Type, List<Type>>(); | |
private static Dictionary<Type, object> singletonMappings = new Dictionary<Type, object>(); | |
private static Stack<Type> injectionStack = new Stack<Type>(); | |
/// <summary> | |
/// Resolves and returns an instance (or an array, if mapped like this) of the specifed type, either | |
/// by getting one from a mapping or by resolving a parameterless ctor | |
/// </summary> | |
/// <param name="type">The type you want to have an instance of</param> | |
/// <returns>A resolved (recursive) instance or array of the specifed type</returns> | |
public static object Get(Type type) { | |
// Keep track of the injection hierarchy | |
injectionStack.Push(type); | |
// Disallow value types and pointer types (more to add?) | |
if(type.IsValueType || type.IsPointer) { | |
throw new InjectionStackException(injectionStack); | |
} | |
// List of object to return for the specified type | |
var resolvedDependencies = new List<object>(); | |
// Check if it has any "singleton" object mapped, and add it if so | |
if(singletonMappings.Keys.Contains(type)) { | |
resolvedDependencies.Add(singletonMappings[type]); | |
// else, preform a type resolution | |
} else { | |
// Find the corresponding type(s) mapped to, or just the same as the one passed by parameter | |
var targets = ResolveType(type); | |
// Iterate over each resolved types | |
foreach(var target in targets) { | |
// Get the contructors of this type | |
var ctors = target.GetConstructors(); | |
// Throw an exception if it doesn't have any constructor | |
if(ctors.Count() <= 0) | |
throw new InjectionStackException(injectionStack); | |
// Get the first constructor found (no support for more than one ctor) | |
var ctor = ctors[0]; | |
// Get the paramters of that ctor | |
var parameters = ctor.GetParameters(); | |
// An array to keep all the resolved parameters | |
var resolvedParams = new List<object>(); | |
// Iterate over each paramter of the ctor | |
foreach(var p in parameters) { | |
// Recursively resolve the type of the ctor's parameter against our mappings | |
var param = Get(p.ParameterType); | |
/// If the paramter is expected to be an array, then it should be converted to the matching array type | |
/// as arrays are not contravariant (at least with the constructor.Invoke) | |
if(p.ParameterType.IsArray) { | |
// Dynamically "cast" the array of parameters into their parent type | |
object[] paramArray; | |
try { | |
paramArray = ((object[])param); | |
} catch(InvalidCastException) { | |
paramArray = new object[] { param }; | |
} | |
// Create the instance of our typed array to hold the parameters | |
var typedParams = Array.CreateInstance(p.ParameterType.GetElementType(), paramArray.Count()); | |
// Copy the data of the object[] params array into the typed one | |
paramArray.CopyTo(typedParams, 0); | |
resolvedParams.Add(typedParams); | |
} else | |
resolvedParams.Add(param); | |
} | |
// Final goal here: invoke the ctor with all the resolved parameters | |
resolvedDependencies.Add(ctor.Invoke(resolvedParams.ToArray())); | |
} | |
} | |
// The injection chain is terminated, clear the injection stack | |
injectionStack.Clear(); | |
// Return the dependencies as an array or as a single object if needed | |
return resolvedDependencies.Count > 1 ? resolvedDependencies.ToArray() : resolvedDependencies[0]; | |
} | |
/// <summary> | |
/// Maps a type to another one (the types can come from runtime data), usually an interface to a concrete class | |
/// </summary> | |
/// <param name="typeT">The key type representing the mapping</param> | |
/// <param name="typeV">The value type to be mapped to the key type</param> | |
public static void Map(Type typeT, Type typeV) { | |
if(mappings.Keys.Contains(typeT)) | |
mappings[typeT].Add(typeV); | |
else | |
mappings.Add(typeT, new List<Type> { typeV }); | |
} | |
/// <summary> | |
/// Maps a generic type to another one (the types must be known at compile time), usually an interface to a concrete class | |
/// </summary> | |
/// <typeparam name="T">The key type representing the mapping</typeparam> | |
/// <typeparam name="V">The value type to be mapped to the key type</typeparam> | |
public static void Map<T, V>() where V : T { | |
Map(typeof(T), typeof(V)); | |
} | |
/// <summary> | |
/// Maps a generic type to type (First must be know at compile time, other can be resolved at runtime), usually an interface to a concrete class | |
/// </summary> | |
/// <typeparam name="T">The key type representing the mapping</typeparam> | |
/// <param name="typeV">The value type to be mapped to the key type</param> | |
public static void Map<T>(Type typeV) { | |
Map(typeof(T), typeV); | |
} | |
/// <summary> | |
/// Maps a type to a singleton object, the mapped object will not be dynamically invoked but a reference will be returned | |
/// </summary> | |
/// <typeparam name="T">The key type representing the mapping</typeparam> | |
/// <param name="o">The singleton object to map to the specifed generic type</param> | |
public static void Map<T>(object o) { | |
singletonMappings.Add(typeof(T), o); | |
} | |
/// <summary> | |
/// Resolves a type by trying to find it inside explicit declared mappings, if the mapping doesn't | |
/// exists it returns the original parameter type | |
/// </summary> | |
/// <param name="type">The type to resolve against mappings</param> | |
/// <returns>An array of mapped types (or a single one)</returns> | |
private static Type[] ResolveType(Type type) { | |
// If the requested type is an array of something | |
if(type.IsArray) | |
// get the underlying the of the array | |
type = type.GetElementType(); | |
// Compare against the mapping if a type matches | |
if(mappings.Keys.Contains(type)) | |
// If so return the matching element(s) | |
return mappings[type].ToArray(); | |
// Return anyway an array of the orginial type (or underlying one) | |
return new Type[] { type }; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment