Add dyanamic factory implementation, with Bind(...).ToAbstractFactory() syntax

This commit is contained in:
Antony Male 2014-02-17 12:47:29 +00:00
parent 396d55098a
commit 3639289b93
4 changed files with 224 additions and 2 deletions

1
.gitignore vendored
View File

@ -36,3 +36,4 @@ packages/
installer/Output
*.gjq
*.tmp

View File

@ -5,6 +5,7 @@ using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -33,6 +34,7 @@ namespace Stylet
void To<TImplementation>(string key = null) where TImplementation : class;
void To(Type implementationType, string key = null);
void ToFactory<TImplementation>(Func<IKernel, TImplementation> factory, string key = null) where TImplementation : class;
void ToAbstractFactory(string key = null);
void ToAllImplementations(string key = null, params Assembly[] assembly);
}
@ -45,12 +47,23 @@ namespace Stylet
{
#region Main Class
public static readonly string FactoryAssemblyName = "StyletIoCFactory";
private ConcurrentDictionary<TypeKey, IRegistrationCollection> registrations = new ConcurrentDictionary<TypeKey, IRegistrationCollection>();
private ConcurrentDictionary<TypeKey, IRegistration> getAllRegistrations = new ConcurrentDictionary<TypeKey, IRegistration>();
// The list object is used for locking it
private ConcurrentDictionary<TypeKey, List<UnboundGeneric>> unboundGenerics = new ConcurrentDictionary<TypeKey, List<UnboundGeneric>>();
private ModuleBuilder factoryBuilder;
private ConcurrentDictionary<Type, Type> factories = new ConcurrentDictionary<Type, Type>();
private bool compilationStarted;
public StyletIoC()
{
this.BindSingleton<IKernel>().ToFactory(c => this);
}
public void AutoBind(params Assembly[] assemblies)
{
if (assemblies == null || assemblies.Length == 0)
@ -318,6 +331,105 @@ namespace Stylet
}
}
private Type GetFactoryForType(Type serviceType)
{
if (!serviceType.IsInterface)
throw new StyletIoCCreateFactoryException(String.Format("Unable to create a factory implementing type {0}, as it isn't an interface", serviceType.Name));
// Have we built it already?
Type factoryType;
if (this.factories.TryGetValue(serviceType, out factoryType))
return factoryType;
if (this.factoryBuilder == null)
{
var assemblyName = new AssemblyName(FactoryAssemblyName);
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("StyletIoCFactoryModule");
Interlocked.CompareExchange(ref this.factoryBuilder, moduleBuilder, null);
}
// If the service is 'ISomethingFactory', call out new class 'SomethingFactory'
var typeBuilder = this.factoryBuilder.DefineType(serviceType.Name.Substring(1), TypeAttributes.Public);
typeBuilder.AddInterfaceImplementation(serviceType);
// Define a field which holds a reference to this ioc container
var containerField = typeBuilder.DefineField("container", typeof(IKernel), FieldAttributes.Private);
// Add a constructor which takes one argument - the container - and sets the field
// public Name(IKernel container)
// {
// this.container = container;
// }
var ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { typeof(IKernel) });
var ilGenerator = ctorBuilder.GetILGenerator();
// Load 'this' and the IOC container onto the stack
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldarg_1);
// Store the IOC container in this.container
ilGenerator.Emit(OpCodes.Stfld, containerField);
ilGenerator.Emit(OpCodes.Ret);
// These are needed by all methods, so get them now
// IKernel.Get(Type, string)
var containerGetMethod = typeof(IKernel).GetMethod("Get", new Type[] { typeof(Type), typeof(string) });
// Type.GetTypeFromHandler(RuntimeTypeHandle)
var typeFromHandleMethod = typeof(Type).GetMethod("GetTypeFromHandle");
// Go through each method, emmitting an implementation for each
foreach (var methodInfo in serviceType.GetMethods())
{
var parameters = methodInfo.GetParameters();
if (!(parameters.Length == 0 || (parameters.Length == 1 && parameters[0].ParameterType == typeof(string))))
throw new StyletIoCCreateFactoryException("Can only implement methods with zero arguments, or a single string argument");
if (methodInfo.ReturnType == typeof(void))
throw new StyletIoCCreateFactoryException("Can only implement methods which return something");
var methodBuilder = typeBuilder.DefineMethod(methodInfo.Name, MethodAttributes.Public | MethodAttributes.Virtual, methodInfo.ReturnType, parameters.Select(x => x.ParameterType).ToArray());
var methodIlGenerator = methodBuilder.GetILGenerator();
// Load 'this' onto stack
// Stack: [this]
methodIlGenerator.Emit(OpCodes.Ldarg_0);
// Load value of 'container' field of 'this' onto stack
// Stack: [this.container]
methodIlGenerator.Emit(OpCodes.Ldfld, containerField);
// New local variable which represents type to load
LocalBuilder lb = methodIlGenerator.DeclareLocal(methodInfo.ReturnType);
// Load this onto the stack. This is a RuntimeTypeHandle
// Stack: [this.container, runtimeTypeHandleOfReturnType]
methodIlGenerator.Emit(OpCodes.Ldtoken, lb.LocalType);
// Invoke Type.GetTypeFromHandle with this
// This is equivalent to calling typeof(T)
// Stack: [this.container, typeof(returnType)]
methodIlGenerator.Emit(OpCodes.Call, typeFromHandleMethod);
// Load the given key (if it's a parameter), or null if it isn't, onto the stack
// Stack: [this.container, typeof(returnType), key]
if (parameters.Length == 0)
methodIlGenerator.Emit(OpCodes.Ldnull); // Load null as the key
else
methodIlGenerator.Emit(OpCodes.Ldarg_1); // Load the given string as the key
// Call container.Get(type, key)
// Stack: [returnedInstance]
methodIlGenerator.Emit(OpCodes.Callvirt, containerGetMethod);
methodIlGenerator.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(methodBuilder, methodInfo);
}
Type constructedType;
try
{
constructedType = typeBuilder.CreateType();
}
catch (TypeLoadException e)
{
throw new StyletIoCCreateFactoryException(String.Format("Unable to create factory type for interface {0}. Ensure that the interface is public, or add [assembly: InternalsVisibleTo(StyletIoC.FactoryAssemblyName)] to your AssemblyInfo.cs", serviceType.Name), e);
}
var actualType = this.factories.GetOrAdd(serviceType, constructedType);
return actualType;
}
#endregion
#region BindTo
@ -370,6 +482,12 @@ namespace Stylet
this.AddRegistration(creator, implementationType, key);
}
public void ToAbstractFactory(string key = null)
{
var factoryType = this.service.GetFactoryForType(this.serviceType);
this.To(factoryType, key);
}
public void ToAllImplementations(string key = null, params Assembly[] assemblies)
{
if (assemblies == null || assemblies.Length == 0)
@ -550,7 +668,7 @@ namespace Stylet
var init = Expression.ListInit(list, container.GetRegistrations(new TypeKey(this.Type.GenericTypeArguments[0], this.Key), false).GetAll().Select(x => x.GetInstanceExpression(container)));
this.expression = init;
return init;
return this.expression;
}
}
@ -660,7 +778,10 @@ namespace Stylet
private string KeyForParameter(ParameterInfo parameter)
{
var attribute = (InjectAttribute)parameter.GetCustomAttributes(typeof(InjectAttribute)).FirstOrDefault();
var attributes = parameter.GetCustomAttributes(typeof(InjectAttribute));
if (attributes == null)
return null;
var attribute = (InjectAttribute)attributes.FirstOrDefault();
return attribute == null ? null : attribute.Key;
}
@ -846,6 +967,12 @@ namespace Stylet
public StyletIoCFindConstructorException(string message, Exception innerException) : base(message, innerException) { }
}
public class StyletIoCCreateFactoryException : StyletIoCException
{
public StyletIoCCreateFactoryException(string message) : base(message) { }
public StyletIoCCreateFactoryException(string message, Exception innerException) : base(message, innerException) { }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Parameter, Inherited = false, AllowMultiple = false)]
public sealed class InjectAttribute : Attribute
{

View File

@ -0,0 +1,93 @@
using NUnit.Framework;
using Stylet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StyletUnitTests
{
[TestFixture]
public class StyletIoCFactoryTests
{
public interface I1 { }
public class C1 : I1 { }
public interface I1Factory
{
I1 GetI1();
}
public interface I1Factory2
{
I1 GetI1(string key = null);
}
interface IPrivateFactory
{
}
public interface IFactoryWithBadMethod
{
C1 MethodWithArgs(bool arg);
}
public interface IFactoryWithVoidMethod
{
void Method();
}
[Test]
public void CreatesImplementationWithoutKey()
{
var ioc = new StyletIoC();
ioc.Bind<I1>().To<C1>();
ioc.Bind<I1Factory>().ToAbstractFactory();
var factory = ioc.Get<I1Factory>();
var result = factory.GetI1();
Assert.IsInstanceOf<C1>(result);
}
[Test]
public void CreatesImplementationWithKey()
{
var ioc = new StyletIoC();
ioc.Bind<I1>().To<C1>("key");
ioc.Bind<I1Factory2>().ToAbstractFactory();
var factory = ioc.Get<I1Factory2>();
var result = factory.GetI1("key");
Assert.IsInstanceOf<C1>(result);
}
[Test]
public void ThrowsIfServiceTypeIsNotInterface()
{
var ioc = new StyletIoC();
Assert.Throws<StyletIoCCreateFactoryException>(() => ioc.Bind<C1>().ToAbstractFactory());
}
[Test]
public void ThrowsIfInterfaceNotPublic()
{
var ioc = new StyletIoC();
Assert.Throws<StyletIoCCreateFactoryException>(() => ioc.Bind<IPrivateFactory>().ToAbstractFactory());
}
[Test]
public void ThrowsIfMethodHasArgumentOtherThanString()
{
var ioc = new StyletIoC();
Assert.Throws<StyletIoCCreateFactoryException>(() => ioc.Bind<IFactoryWithBadMethod>().ToAbstractFactory());
}
[Test]
public void ThrowsIfMethodReturningVoid()
{
var ioc = new StyletIoC();
Assert.Throws<StyletIoCCreateFactoryException>(() => ioc.Bind<IFactoryWithVoidMethod>().ToAbstractFactory());
}
}
}

View File

@ -49,6 +49,7 @@
<Compile Include="StyletIoCAutobindingTests.cs" />
<Compile Include="StyletIoCBindingChecksTests.cs" />
<Compile Include="StyletIoCConstructorInjectionTests.cs" />
<Compile Include="StyletIoCFactoryTests.cs" />
<Compile Include="StyletIoCGetAllTests.cs" />
<Compile Include="StyletIoCGetSingleKeyedTests.cs" />
<Compile Include="StyletIoCGetSingleTests.cs" />