Skip to content

Instantly share code, notes, and snippets.

@japf
Created August 12, 2016 12:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save japf/62dcd40f3dab1e9956b21dfa1e2401d0 to your computer and use it in GitHub Desktop.
Save japf/62dcd40f3dab1e9956b21dfa1e2401d0 to your computer and use it in GitHub Desktop.
A helper class that can create object instance that have dependencies to interface by guessing class that implement those interfaces.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Practices.Unity;
namespace MyApp.Tools
{
/// <summary>
/// An helper class that is smart enough to create an instance of an object from its interface - automatically
/// It works by finding all types that implements an interface and have a public constructor. By using reflection
/// we find out what parameters are needed to call the constructor and we recursively create all instances.
/// </summary>
/// <remarks>
/// This class is NOT intended to be used in production environment but rather in tools or integration tests.
/// </remarks>
[ExcludeFromCodeCoverage]
public static class SmartObjectBuilder
{
private const string AssemblyFilter = "YourCustomNamespace";
private static Dictionary<Type, Type> implementations; // map interface type to implementation type (ex: <IFoo, Foo>)
private static Dictionary<Type, object> instances; // map interface type to actual implementation instances (ex: <IFoo, instance of object implementing IFoo>)
public static Action<string> LogHandler { get; set; }
/// <summary>
/// Create a new instance of a given type from its interface by dynamically inspecting its constructor and recursively fill
/// all parameters with their appropriate implementation. If the given type is an interface, the method automatically finds
/// a type that implements it and use it as type to create.
/// </summary>
/// <typeparam name="T">Type (concrete of interface) to create</typeparam>
/// <returns>An instance of the newly instanced object</returns>
public static T Build<T>() where T : class
{
EnsureCacheIsBuilt();
var type = typeof(T);
if (!type.IsInterface)
{
ConstructorInfo constructor = type.GetConstructors()[0];
List<object> parameters = new List<object>();
foreach (var parameterInfo in constructor.GetParameters())
{
if (!parameterInfo.ParameterType.IsInterface)
{
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Could not instantiate constructor because parameter {0} is not an interface", parameterInfo.ParameterType.Name));
}
if (!instances.ContainsKey(parameterInfo.ParameterType))
{
Build(parameterInfo.ParameterType);
}
parameters.Add(instances[parameterInfo.ParameterType]);
}
return (T)constructor.Invoke(parameters.ToArray());
}
else
{
Build(type);
return (T)instances[type];
}
}
private static void Build(Type interfaceType)
{
EnsureCacheIsBuilt();
if (!interfaceType.IsInterface)
{
throw new NotSupportedException("This method can only build interface type");
}
if (!implementations.ContainsKey(interfaceType))
{
throw new NotSupportedException("This type is not known");
}
if (instances.ContainsKey(interfaceType))
{
return;
}
if (UnityContainer.Instance.IsRegistered(interfaceType))
{
instances.Add(interfaceType, UnityContainer.Instance.Resolve(interfaceType));
return;
}
Type concreteType = implementations[interfaceType];
ConstructorInfo constructor = concreteType.GetConstructors()[0];
List<object> parameters = new List<object>();
foreach (var parameterInfo in constructor.GetParameters())
{
var parameterType = parameterInfo.ParameterType;
if (!instances.ContainsKey(parameterType))
{
Build(parameterType);
}
parameters.Add(instances[parameterType]);
}
if (LogHandler != null)
{
LogHandler(string.Format(CultureInfo.InvariantCulture, "Building {0}", interfaceType.Name));
}
object instance = constructor.Invoke(parameters.ToArray());
instances.Add(interfaceType, instance);
if (!UnityContainer.Instance.IsRegistered(interfaceType))
{
UnityContainer.Instance.RegisterInstance(interfaceType, instance);
}
}
private static void EnsureCacheIsBuilt()
{
if (implementations != null && instances != null)
{
return;
}
try
{
BuildCache();
}
catch (Exception ex)
{
Debug.WriteLine("Failed to build cache: " + ex);
throw;
}
}
private static void BuildCache()
{
implementations = new Dictionary<Type, Type>();
instances = new Dictionary<Type, object>();
foreach (string path in Directory.GetFiles(".", "*.dll").Where(p => p.Contains(AssemblyFilter)))
{
var assembly = Assembly.LoadFrom(path);
var types = assembly.GetTypes().Where(t => t.IsClass && t.GetInterfaces().Length > 0 && t.GetConstructors().Length == 1).ToList();
foreach (Type type in types)
{
foreach (var interfaceType in type.GetInterfaces())
{
ConstructorInfo constructor = type.GetConstructors().FirstOrDefault(c => c.GetParameters().All(p => p.ParameterType.IsInterface));
if (constructor != null && !implementations.ContainsKey(interfaceType))
{
implementations.Add(interfaceType, type);
}
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment