Skip to content

Add non-generic overload of CreateInstance #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions LazyProxy.Tests/LazyProxyBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,37 @@ public void GenericInterfacesMustBeProxied()
Assert.Equal(expectedResult2, actualResult2);
}

[Fact]
public void GenericInterfacesMustBeProxiedByNonGenericMethod()
{
var arg1 = new TestArgument2();
var arg2 = new TestArgument();
var expectedResult1 = new TestArgument4();
var expectedResult2 = new TestArgument2();

var proxyObject = LazyProxyBuilder.CreateInstance(
typeof(IGenericTestService<TestArgument2, TestArgument, TestArgument4>), () =>
{
var mock = new Mock<IGenericTestService<TestArgument2, TestArgument, TestArgument4>>(
MockBehavior.Strict);

mock.Setup(s => s.Method1(arg1, arg2)).Returns(expectedResult1);
mock.Setup(s => s.Method2(arg1, arg2)).Returns(expectedResult2);

return mock.Object;
});

var proxy = proxyObject as IGenericTestService<TestArgument2, TestArgument, TestArgument4>;

Assert.NotNull(proxy);

var actualResult1 = proxy.Method1(arg1, arg2);
var actualResult2 = proxy.Method2(arg1, arg2);

Assert.Equal(expectedResult1, actualResult1);
Assert.Equal(expectedResult2, actualResult2);
}

[Fact]
public void GenericInterfaceWithDifferentTypeParametersMustBeCreatedWithoutExceptions()
{
Expand Down
21 changes: 21 additions & 0 deletions LazyProxy/LazyBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;

namespace LazyProxy
{
/// <summary>
/// This type hosts factory method that creates <see cref="Lazy{T}"/> instances.
/// </summary>
public static class LazyBuilder
{
/// <summary>
/// Creates an instance of <see cref="Lazy{T}"/>.
/// </summary>
/// <param name="valueFactory">Function that returns a value.</param>
/// <typeparam name="T">Type of lazy value.</typeparam>
/// <returns>Instance of <see cref="Lazy{T}"/></returns>
public static Lazy<T> CreateInstance<T>(Func<object> valueFactory)
{
return new Lazy<T>(() => (T) valueFactory());
}
}
}
16 changes: 16 additions & 0 deletions LazyProxy/LazyProxyBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

namespace LazyProxy
{
/// <summary>
/// Base class for lazy proxies.
/// </summary>
public abstract class LazyProxyBase
{
/// <summary>
/// Initializes inner <see cref="Lazy{T}"/> instance with the valueFactory provided.
/// </summary>
/// <param name="valueFactory">Function that returns a value.</param>
public abstract void Initialize(Func<object> valueFactory);
}
}
65 changes: 48 additions & 17 deletions LazyProxy/LazyProxyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ public static class LazyProxyBuilder
private static readonly ConcurrentDictionary<Type, Lazy<Type>> ProxyTypes =
new ConcurrentDictionary<Type, Lazy<Type>>();

private static readonly MethodInfo CreateLazyMethod = typeof(LazyBuilder)
.GetMethod("CreateInstance", BindingFlags.Public | BindingFlags.Static);

/// <summary>
/// Defines at runtime a class that implements interface T
/// and proxies all invocations to <see cref="Lazy{T}"/> of this interface.
/// </summary>
/// <typeparam name="T">The interface proxy type implements.</typeparam>
/// <returns>The lazy proxy type.</returns>
public static Type GetType<T>()
public static Type GetType<T>() where T : class
{
return GetType(typeof(T));
}
Expand All @@ -50,10 +53,18 @@ public static Type GetType(Type type)
throw new NotSupportedException("The lazy proxy is supported only for interfaces.");
}

var interfaceType = type.IsConstructedGenericType
? type.GetGenericTypeDefinition()
: type;

// Lazy is used to guarantee the valueFactory is invoked only once.
// More info: http://reedcopsey.com/2011/01/16/concurrentdictionarytkeytvalue-used-with-lazyt/
var lazy = ProxyTypes.GetOrAdd(type, t => new Lazy<Type>(() => DefineProxyType(t)));
return lazy.Value;
var lazy = ProxyTypes.GetOrAdd(interfaceType, t => new Lazy<Type>(() => DefineProxyType(t)));
var proxyType = lazy.Value;

return type.IsConstructedGenericType
? proxyType.MakeGenericType(type.GetGenericArguments())
: proxyType;
}

/// <summary>
Expand All @@ -62,11 +73,27 @@ public static Type GetType(Type type)
/// <param name="valueFactory">The function real value returns.</param>
/// <typeparam name="T">The interface proxy type implements.</typeparam>
/// <returns>The lazy proxy type instance.</returns>
public static T CreateInstance<T>(Func<T> valueFactory)
public static T CreateInstance<T>(Func<T> valueFactory) where T : class
{
var lazy = new Lazy<T>(valueFactory);
var proxyType = GetType<T>();
return (T) Activator.CreateInstance(proxyType, lazy);
return (T) CreateInstance(typeof(T), valueFactory);
}

/// <summary>
/// Creates a lazy proxy type instance using a value factory.
/// </summary>
/// <param name="type">The interface proxy type implements.</param>
/// <param name="valueFactory">The function real value returns.</param>
/// <returns>The lazy proxy type instance.</returns>
public static object CreateInstance(Type type, Func<object> valueFactory)
{
var proxyType = GetType(type);

// Using 'Initialize' method after the instance creation allows to improve performance
// because Activator.CreateInstance executed with arguments is much slower.
var instance = (LazyProxyBase) Activator.CreateInstance(proxyType);
instance.Initialize(valueFactory);

return instance;
}

/// <summary>
Expand All @@ -81,9 +108,9 @@ public static T CreateInstance<T>(Func<T> valueFactory)
/// {
/// private Lazy<IMyService> _service;
///
/// public LazyProxyImpl_1eb94ccd79fd48af8adfbc97c76c10ff_IMyService(Lazy<IMyService> service)
/// public void Initialize(Func<object> valueFactory)
/// {
/// _service = service;
/// _service = LazyBuilder.CreateInstance<IMyService>(valueFactory);
/// }
///
/// public void Foo() => _service.Value.Foo();
Expand All @@ -101,11 +128,11 @@ private static Type DefineProxyType(Type type)

var typeName = $"{type.Namespace}.{LazyProxyTypeSuffix}_{guid}_{type.Name}";

return ModuleBuilder.DefineType(typeName, TypeAttributes.Public)
return ModuleBuilder.DefineType(typeName, TypeAttributes.Public, typeof(LazyProxyBase))
.AddGenericParameters(type)
.AddInterface(type)
.AddServiceField(type, out var serviceField)
.AddConstructor(type, serviceField)
.AddInitializeMethod(type, serviceField)
.AddMethods(type, serviceField)
.CreateTypeInfo();
}
Expand Down Expand Up @@ -137,18 +164,22 @@ private static TypeBuilder AddServiceField(this TypeBuilder typeBuilder,
return typeBuilder;
}

private static TypeBuilder AddConstructor(this TypeBuilder typeBuilder, Type type, FieldInfo serviceField)
private static TypeBuilder AddInitializeMethod(this TypeBuilder typeBuilder, Type type, FieldInfo serviceField)
{
var constructorBuilder = typeBuilder.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
new[] {typeof(Lazy<>).MakeGenericType(type)}
var methodBuilder = typeBuilder.DefineMethod(
"Initialize",
MethodAttributes.Public | MethodAttributes.Virtual,
null,
new [] { typeof(Func<object>) }
);

var generator = constructorBuilder.GetILGenerator();
var createLazyMethod = CreateLazyMethod.MakeGenericMethod(type);

var generator = methodBuilder.GetILGenerator();

generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Call, createLazyMethod);
generator.Emit(OpCodes.Stfld, serviceField);
generator.Emit(OpCodes.Ret);

Expand Down