Skip to content

Commit f345fbf

Browse files
Merge pull request #5 from servicetitan/supporting-open-generic
Add non-generic overload of CreateInstance
2 parents 4bdee60 + c951fa5 commit f345fbf

File tree

4 files changed

+116
-17
lines changed

4 files changed

+116
-17
lines changed

LazyProxy.Tests/LazyProxyBuilderTests.cs

+31
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,37 @@ public void GenericInterfacesMustBeProxied()
363363
Assert.Equal(expectedResult2, actualResult2);
364364
}
365365

366+
[Fact]
367+
public void GenericInterfacesMustBeProxiedByNonGenericMethod()
368+
{
369+
var arg1 = new TestArgument2();
370+
var arg2 = new TestArgument();
371+
var expectedResult1 = new TestArgument4();
372+
var expectedResult2 = new TestArgument2();
373+
374+
var proxyObject = LazyProxyBuilder.CreateInstance(
375+
typeof(IGenericTestService<TestArgument2, TestArgument, TestArgument4>), () =>
376+
{
377+
var mock = new Mock<IGenericTestService<TestArgument2, TestArgument, TestArgument4>>(
378+
MockBehavior.Strict);
379+
380+
mock.Setup(s => s.Method1(arg1, arg2)).Returns(expectedResult1);
381+
mock.Setup(s => s.Method2(arg1, arg2)).Returns(expectedResult2);
382+
383+
return mock.Object;
384+
});
385+
386+
var proxy = proxyObject as IGenericTestService<TestArgument2, TestArgument, TestArgument4>;
387+
388+
Assert.NotNull(proxy);
389+
390+
var actualResult1 = proxy.Method1(arg1, arg2);
391+
var actualResult2 = proxy.Method2(arg1, arg2);
392+
393+
Assert.Equal(expectedResult1, actualResult1);
394+
Assert.Equal(expectedResult2, actualResult2);
395+
}
396+
366397
[Fact]
367398
public void GenericInterfaceWithDifferentTypeParametersMustBeCreatedWithoutExceptions()
368399
{

LazyProxy/LazyBuilder.cs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
3+
namespace LazyProxy
4+
{
5+
/// <summary>
6+
/// This type hosts factory method that creates <see cref="Lazy{T}"/> instances.
7+
/// </summary>
8+
public static class LazyBuilder
9+
{
10+
/// <summary>
11+
/// Creates an instance of <see cref="Lazy{T}"/>.
12+
/// </summary>
13+
/// <param name="valueFactory">Function that returns a value.</param>
14+
/// <typeparam name="T">Type of lazy value.</typeparam>
15+
/// <returns>Instance of <see cref="Lazy{T}"/></returns>
16+
public static Lazy<T> CreateInstance<T>(Func<object> valueFactory)
17+
{
18+
return new Lazy<T>(() => (T) valueFactory());
19+
}
20+
}
21+
}

LazyProxy/LazyProxyBase.cs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
3+
namespace LazyProxy
4+
{
5+
/// <summary>
6+
/// Base class for lazy proxies.
7+
/// </summary>
8+
public abstract class LazyProxyBase
9+
{
10+
/// <summary>
11+
/// Initializes inner <see cref="Lazy{T}"/> instance with the valueFactory provided.
12+
/// </summary>
13+
/// <param name="valueFactory">Function that returns a value.</param>
14+
public abstract void Initialize(Func<object> valueFactory);
15+
}
16+
}

LazyProxy/LazyProxyBuilder.cs

+48-17
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ public static class LazyProxyBuilder
2525
private static readonly ConcurrentDictionary<Type, Lazy<Type>> ProxyTypes =
2626
new ConcurrentDictionary<Type, Lazy<Type>>();
2727

28+
private static readonly MethodInfo CreateLazyMethod = typeof(LazyBuilder)
29+
.GetMethod("CreateInstance", BindingFlags.Public | BindingFlags.Static);
30+
2831
/// <summary>
2932
/// Defines at runtime a class that implements interface T
3033
/// and proxies all invocations to <see cref="Lazy{T}"/> of this interface.
3134
/// </summary>
3235
/// <typeparam name="T">The interface proxy type implements.</typeparam>
3336
/// <returns>The lazy proxy type.</returns>
34-
public static Type GetType<T>()
37+
public static Type GetType<T>() where T : class
3538
{
3639
return GetType(typeof(T));
3740
}
@@ -50,10 +53,18 @@ public static Type GetType(Type type)
5053
throw new NotSupportedException("The lazy proxy is supported only for interfaces.");
5154
}
5255

56+
var interfaceType = type.IsConstructedGenericType
57+
? type.GetGenericTypeDefinition()
58+
: type;
59+
5360
// Lazy is used to guarantee the valueFactory is invoked only once.
5461
// More info: http://reedcopsey.com/2011/01/16/concurrentdictionarytkeytvalue-used-with-lazyt/
55-
var lazy = ProxyTypes.GetOrAdd(type, t => new Lazy<Type>(() => DefineProxyType(t)));
56-
return lazy.Value;
62+
var lazy = ProxyTypes.GetOrAdd(interfaceType, t => new Lazy<Type>(() => DefineProxyType(t)));
63+
var proxyType = lazy.Value;
64+
65+
return type.IsConstructedGenericType
66+
? proxyType.MakeGenericType(type.GetGenericArguments())
67+
: proxyType;
5768
}
5869

5970
/// <summary>
@@ -62,11 +73,27 @@ public static Type GetType(Type type)
6273
/// <param name="valueFactory">The function real value returns.</param>
6374
/// <typeparam name="T">The interface proxy type implements.</typeparam>
6475
/// <returns>The lazy proxy type instance.</returns>
65-
public static T CreateInstance<T>(Func<T> valueFactory)
76+
public static T CreateInstance<T>(Func<T> valueFactory) where T : class
6677
{
67-
var lazy = new Lazy<T>(valueFactory);
68-
var proxyType = GetType<T>();
69-
return (T) Activator.CreateInstance(proxyType, lazy);
78+
return (T) CreateInstance(typeof(T), valueFactory);
79+
}
80+
81+
/// <summary>
82+
/// Creates a lazy proxy type instance using a value factory.
83+
/// </summary>
84+
/// <param name="type">The interface proxy type implements.</param>
85+
/// <param name="valueFactory">The function real value returns.</param>
86+
/// <returns>The lazy proxy type instance.</returns>
87+
public static object CreateInstance(Type type, Func<object> valueFactory)
88+
{
89+
var proxyType = GetType(type);
90+
91+
// Using 'Initialize' method after the instance creation allows to improve performance
92+
// because Activator.CreateInstance executed with arguments is much slower.
93+
var instance = (LazyProxyBase) Activator.CreateInstance(proxyType);
94+
instance.Initialize(valueFactory);
95+
96+
return instance;
7097
}
7198

7299
/// <summary>
@@ -81,9 +108,9 @@ public static T CreateInstance<T>(Func<T> valueFactory)
81108
/// {
82109
/// private Lazy<IMyService> _service;
83110
///
84-
/// public LazyProxyImpl_1eb94ccd79fd48af8adfbc97c76c10ff_IMyService(Lazy<IMyService> service)
111+
/// public void Initialize(Func<object> valueFactory)
85112
/// {
86-
/// _service = service;
113+
/// _service = LazyBuilder.CreateInstance<IMyService>(valueFactory);
87114
/// }
88115
///
89116
/// public void Foo() => _service.Value.Foo();
@@ -101,11 +128,11 @@ private static Type DefineProxyType(Type type)
101128

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

104-
return ModuleBuilder.DefineType(typeName, TypeAttributes.Public)
131+
return ModuleBuilder.DefineType(typeName, TypeAttributes.Public, typeof(LazyProxyBase))
105132
.AddGenericParameters(type)
106133
.AddInterface(type)
107134
.AddServiceField(type, out var serviceField)
108-
.AddConstructor(type, serviceField)
135+
.AddInitializeMethod(type, serviceField)
109136
.AddMethods(type, serviceField)
110137
.CreateTypeInfo();
111138
}
@@ -137,18 +164,22 @@ private static TypeBuilder AddServiceField(this TypeBuilder typeBuilder,
137164
return typeBuilder;
138165
}
139166

140-
private static TypeBuilder AddConstructor(this TypeBuilder typeBuilder, Type type, FieldInfo serviceField)
167+
private static TypeBuilder AddInitializeMethod(this TypeBuilder typeBuilder, Type type, FieldInfo serviceField)
141168
{
142-
var constructorBuilder = typeBuilder.DefineConstructor(
143-
MethodAttributes.Public,
144-
CallingConventions.Standard,
145-
new[] {typeof(Lazy<>).MakeGenericType(type)}
169+
var methodBuilder = typeBuilder.DefineMethod(
170+
"Initialize",
171+
MethodAttributes.Public | MethodAttributes.Virtual,
172+
null,
173+
new [] { typeof(Func<object>) }
146174
);
147175

148-
var generator = constructorBuilder.GetILGenerator();
176+
var createLazyMethod = CreateLazyMethod.MakeGenericMethod(type);
177+
178+
var generator = methodBuilder.GetILGenerator();
149179

150180
generator.Emit(OpCodes.Ldarg_0);
151181
generator.Emit(OpCodes.Ldarg_1);
182+
generator.Emit(OpCodes.Call, createLazyMethod);
152183
generator.Emit(OpCodes.Stfld, serviceField);
153184
generator.Emit(OpCodes.Ret);
154185

0 commit comments

Comments
 (0)