From 3e04cb5d106b90e18d388a9bf3293ce99db7e629 Mon Sep 17 00:00:00 2001 From: Andrew Davey Date: Fri, 4 May 2012 19:01:05 +0100 Subject: [PATCH] Add support for resolving factory delegates. --- src/TinyIoC.Tests/TinyIoC.Tests.csproj | 1 + .../TinyIoCResolveFactoryDelegateTests.cs | 50 +++++++ src/TinyIoC/TinyIoC.cs | 132 +++++++++++++++++- 3 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 src/TinyIoC.Tests/TinyIoCResolveFactoryDelegateTests.cs diff --git a/src/TinyIoC.Tests/TinyIoC.Tests.csproj b/src/TinyIoC.Tests/TinyIoC.Tests.csproj index fdb0072..64135bd 100644 --- a/src/TinyIoC.Tests/TinyIoC.Tests.csproj +++ b/src/TinyIoC.Tests/TinyIoC.Tests.csproj @@ -77,6 +77,7 @@ + diff --git a/src/TinyIoC.Tests/TinyIoCResolveFactoryDelegateTests.cs b/src/TinyIoC.Tests/TinyIoCResolveFactoryDelegateTests.cs new file mode 100644 index 0000000..a8dd85d --- /dev/null +++ b/src/TinyIoC.Tests/TinyIoCResolveFactoryDelegateTests.cs @@ -0,0 +1,50 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TinyIoC.Tests.TestData; + +namespace TinyIoC.Tests +{ + [TestClass] + public class TinyIoCResolveFactoryDelegateTests + { + class Example + { + public delegate Example Factory(int value); + + public Example(int value, Dependency dependency) + { + Value = value; + Dependency = dependency; + } + + public int Value { get; private set; } + public Dependency Dependency { get; private set; } + } + + class Dependency + { + } + + [TestMethod] + public void Resolve_FactoryDelegate_ReturnsDelegateThatCanConstructInstance() + { + var container = UtilityMethods.GetContainer(); + + var factory = container.Resolve(); + var createdObject = factory(1); + + Assert.AreEqual(1, createdObject.Value); + Assert.IsNotNull(createdObject.Dependency); + } + + [TestMethod] + public void Resolve_FactoryDelegate_CachesDelegate() + { + var container = UtilityMethods.GetContainer(); + + var factory1 = container.Resolve(); + var factory2 = container.Resolve(); + + Assert.AreSame(factory1, factory2); + } + } +} \ No newline at end of file diff --git a/src/TinyIoC/TinyIoC.cs b/src/TinyIoC/TinyIoC.cs index 3903ded..cd3bcca 100644 --- a/src/TinyIoC/TinyIoC.cs +++ b/src/TinyIoC/TinyIoC.cs @@ -2986,8 +2986,12 @@ private bool IsIEnumerableRequest(Type type) private bool IsAutomaticLazyFactoryRequest(Type type) { + var isDelegate = typeof (Delegate).IsAssignableFrom(type); + if (!type.IsGenericType) - return false; + { + return isDelegate; + } Type genericType = type.GetGenericTypeDefinition(); @@ -3003,6 +3007,9 @@ private bool IsAutomaticLazyFactoryRequest(Type type) if ((genericType == typeof(Func<,,>) && type.GetGenericArguments()[0] == typeof(string) && type.GetGenericArguments()[1] == typeof(IDictionary))) return true; + if (isDelegate) + return true; + return false; } @@ -3130,8 +3137,14 @@ private object ResolveInternal(TypeRegistration registration, NamedParameterOver #if EXPRESSIONS private object GetLazyAutomaticFactoryRequest(Type type) { + var isDelegate = typeof(Delegate).IsAssignableFrom(type); + if (!type.IsGenericType) - return null; + { + return isDelegate + ? factoryDelegateBuilder.BuildFactory(type, this) + : null; + } Type genericType = type.GetGenericTypeDefinition(); Type[] genericArguments = type.GetGenericArguments(); @@ -3185,6 +3198,12 @@ private object GetLazyAutomaticFactoryRequest(Type type) return resolveLambda; } + // Any other type of delegate. + if (isDelegate) + { + return factoryDelegateBuilder.BuildFactory(type, this); + } + throw new TinyIoCResolutionException(type); } #endif @@ -3399,5 +3418,114 @@ public void Dispose() } #endregion + + #region Automatic factory delegate builder + + // Example: + // A class where only some constructor arguments should be resolved by the container. + // Add a public factory delegate. + // + // public class Logger + // { + // public delegate Logger Factory(int indent); + // + // public Logger(int indent, IOutput output) + // { + // ... + // } + // } + // + // We can now resolve the factory delegate. + // var loggerFactory = container.Resolve(); + // var logger = loggerFactory(4); + // The created Logger instance will have indent=4 and output={whatever the container provided}. + +#if EXPRESSIONS + readonly FactoryDelegateBuilder factoryDelegateBuilder = new FactoryDelegateBuilder(); + + class FactoryDelegateBuilder + { + static readonly MethodInfo genericResolveMethod = typeof(TinyIoCContainer).GetMethod("Resolve", new[] { typeof(NamedParameterOverloads) }); + static readonly MethodInfo addMethod = typeof(NamedParameterOverloads).GetMethod("Add"); + static readonly ConstructorInfo namedParameterOverloadsConstructor = typeof(NamedParameterOverloads).GetConstructor(new Type[0]); + + readonly Dictionary delegateCache = new Dictionary(); + + public object BuildFactory(Type delegateType, TinyIoCContainer container) + { + lock (delegateCache) + { + Delegate factory; + if (!delegateCache.TryGetValue(delegateType, out factory)) + { + factory = CreateFactoryDelegate(delegateType, container); + delegateCache[delegateType] = factory; + } + return factory; + } + } + + Delegate CreateFactoryDelegate(Type delegateType, object container) + { + // Create a delegate like this: + // (p1, p2, ...) => container.Resolve(new NamedParameterOverloads() { + // { "p1", p1 }, + // { "p2", p2 }, + // ... + // }) + + // So any T constructor parameters not matching factory delegate parameters will be + // resolved from the container. + + var delegateInvokeMethod = delegateType.GetMethod("Invoke"); + var parameters = CreateParameters(delegateInvokeMethod); + var resolveCall = CreateResolveCallExpression(delegateInvokeMethod.ReturnType, parameters, container); + + var lambdaExpression = Expression.Lambda(delegateType, resolveCall, parameters); + + return lambdaExpression.Compile(); + } + + MethodCallExpression CreateResolveCallExpression(Type returnType, IEnumerable parameters, object container) + { + var resolveMethod = genericResolveMethod.MakeGenericMethod(returnType); + var namedParameterOverloads = CreateNamedParameterOverloadsInitializerExpression(parameters); + // container.Resolve(namedParameterOverloads); + return Expression.Call( + Expression.Constant(container), + resolveMethod, + namedParameterOverloads + ); + } + + ListInitExpression CreateNamedParameterOverloadsInitializerExpression(IEnumerable parameters) + { + // new NamedParameterOverloads { { "p1", p1 }, { "p2", p2 }, ... } + return Expression.ListInit( + Expression.New(namedParameterOverloadsConstructor), + parameters.Select(CreateElementInit) + ); + } + + ElementInit CreateElementInit(ParameterExpression parameter) + { + // .Add("parameterName", parameterValue) + return Expression.ElementInit( + addMethod, + Expression.Constant(parameter.Name), + Expression.Convert(parameter, typeof(object)) + ); + } + + static ParameterExpression[] CreateParameters(MethodInfo delegateInvokeMethod) + { + return delegateInvokeMethod + .GetParameters() + .Select(p => Expression.Parameter(p.ParameterType, p.Name)) + .ToArray(); + } + } +#endif + #endregion } }