mirror of https://github.com/AMT-Cheif/Stylet.git
Add dyanamic factory implementation, with Bind(...).ToAbstractFactory() syntax
This commit is contained in:
parent
396d55098a
commit
3639289b93
|
@ -36,3 +36,4 @@ packages/
|
||||||
installer/Output
|
installer/Output
|
||||||
|
|
||||||
*.gjq
|
*.gjq
|
||||||
|
*.tmp
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Reflection.Emit;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -33,6 +34,7 @@ namespace Stylet
|
||||||
void To<TImplementation>(string key = null) where TImplementation : class;
|
void To<TImplementation>(string key = null) where TImplementation : class;
|
||||||
void To(Type implementationType, string key = null);
|
void To(Type implementationType, string key = null);
|
||||||
void ToFactory<TImplementation>(Func<IKernel, TImplementation> factory, string key = null) where TImplementation : class;
|
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);
|
void ToAllImplementations(string key = null, params Assembly[] assembly);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,12 +47,23 @@ namespace Stylet
|
||||||
{
|
{
|
||||||
#region Main Class
|
#region Main Class
|
||||||
|
|
||||||
|
public static readonly string FactoryAssemblyName = "StyletIoCFactory";
|
||||||
|
|
||||||
private ConcurrentDictionary<TypeKey, IRegistrationCollection> registrations = new ConcurrentDictionary<TypeKey, IRegistrationCollection>();
|
private ConcurrentDictionary<TypeKey, IRegistrationCollection> registrations = new ConcurrentDictionary<TypeKey, IRegistrationCollection>();
|
||||||
private ConcurrentDictionary<TypeKey, IRegistration> getAllRegistrations = new ConcurrentDictionary<TypeKey, IRegistration>();
|
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 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;
|
private bool compilationStarted;
|
||||||
|
|
||||||
|
public StyletIoC()
|
||||||
|
{
|
||||||
|
this.BindSingleton<IKernel>().ToFactory(c => this);
|
||||||
|
}
|
||||||
|
|
||||||
public void AutoBind(params Assembly[] assemblies)
|
public void AutoBind(params Assembly[] assemblies)
|
||||||
{
|
{
|
||||||
if (assemblies == null || assemblies.Length == 0)
|
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
|
#endregion
|
||||||
|
|
||||||
#region BindTo
|
#region BindTo
|
||||||
|
@ -370,6 +482,12 @@ namespace Stylet
|
||||||
this.AddRegistration(creator, implementationType, key);
|
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)
|
public void ToAllImplementations(string key = null, params Assembly[] assemblies)
|
||||||
{
|
{
|
||||||
if (assemblies == null || assemblies.Length == 0)
|
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)));
|
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;
|
this.expression = init;
|
||||||
return init;
|
return this.expression;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -660,7 +778,10 @@ namespace Stylet
|
||||||
|
|
||||||
private string KeyForParameter(ParameterInfo parameter)
|
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;
|
return attribute == null ? null : attribute.Key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -846,6 +967,12 @@ namespace Stylet
|
||||||
public StyletIoCFindConstructorException(string message, Exception innerException) : base(message, innerException) { }
|
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)]
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Parameter, Inherited = false, AllowMultiple = false)]
|
||||||
public sealed class InjectAttribute : Attribute
|
public sealed class InjectAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,6 +49,7 @@
|
||||||
<Compile Include="StyletIoCAutobindingTests.cs" />
|
<Compile Include="StyletIoCAutobindingTests.cs" />
|
||||||
<Compile Include="StyletIoCBindingChecksTests.cs" />
|
<Compile Include="StyletIoCBindingChecksTests.cs" />
|
||||||
<Compile Include="StyletIoCConstructorInjectionTests.cs" />
|
<Compile Include="StyletIoCConstructorInjectionTests.cs" />
|
||||||
|
<Compile Include="StyletIoCFactoryTests.cs" />
|
||||||
<Compile Include="StyletIoCGetAllTests.cs" />
|
<Compile Include="StyletIoCGetAllTests.cs" />
|
||||||
<Compile Include="StyletIoCGetSingleKeyedTests.cs" />
|
<Compile Include="StyletIoCGetSingleKeyedTests.cs" />
|
||||||
<Compile Include="StyletIoCGetSingleTests.cs" />
|
<Compile Include="StyletIoCGetSingleTests.cs" />
|
||||||
|
|
Loading…
Reference in New Issue