Skip to content

Instantly share code, notes, and snippets.

@vkobel
Last active August 29, 2015 14:05
Show Gist options
  • Save vkobel/48bf6b470346cf590e92 to your computer and use it in GitHub Desktop.
Save vkobel/48bf6b470346cf590e92 to your computer and use it in GitHub Desktop.
Simple Dependency Injector (contructors only) in .NET C#
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