Skip to content

Commit b873879

Browse files
authored
Merge pull request #7 from servicetitan/disposable-implementation-fix
Do not call Dispose method in case proxied instance not created
2 parents f39edb5 + e2281b4 commit b873879

File tree

5 files changed

+141
-27
lines changed

5 files changed

+141
-27
lines changed
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using Moq;
3+
using Xunit;
4+
5+
namespace LazyProxy.Tests
6+
{
7+
public class DisposableLazyTests
8+
{
9+
public interface IHaveDisposeBusinessMethod
10+
{
11+
void Dispose();
12+
}
13+
14+
public interface IImplementIDisposable: IDisposable
15+
{
16+
void DoSomething();
17+
}
18+
19+
[Fact]
20+
public void BusinessDisposeMethodIsCalled()
21+
{
22+
var mock = new Mock<IHaveDisposeBusinessMethod>(MockBehavior.Strict);
23+
mock.Setup(s => s.Dispose()).Verifiable();
24+
25+
var proxy = LazyProxyBuilder.CreateInstance(() => mock.Object);
26+
27+
proxy.Dispose();
28+
29+
mock.Verify(s => s.Dispose());
30+
}
31+
32+
[Fact]
33+
public void RegularDisposeMethodIsCalledIfInstanceIsCreated()
34+
{
35+
var mock = new Mock<IImplementIDisposable>(MockBehavior.Strict);
36+
mock.Setup(s => s.DoSomething());
37+
mock.Setup(s => s.Dispose()).Verifiable();
38+
39+
var proxy = LazyProxyBuilder.CreateInstance(() => mock.Object);
40+
41+
proxy.DoSomething();
42+
proxy.Dispose();
43+
44+
mock.Verify(s => s.Dispose());
45+
}
46+
47+
[Fact]
48+
public void RegularDisposeMethodIsNotCalledIfNoInstanceIsCreated()
49+
{
50+
var mock = new Mock<IImplementIDisposable>(MockBehavior.Strict);
51+
var callCounter = 0;
52+
mock.Setup(s => s.Dispose()).Callback(() => callCounter++);
53+
54+
var proxy = LazyProxyBuilder.CreateInstance(() => mock.Object);
55+
56+
proxy.Dispose();
57+
58+
Assert.Equal(0, callCounter);
59+
}
60+
}
61+
}

LazyProxy/LazyBuilder.cs

-21
This file was deleted.

LazyProxy/LazyProxyBase.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ public abstract class LazyProxyBase
1111
/// Initializes inner <see cref="Lazy{T}"/> instance with the valueFactory provided.
1212
/// </summary>
1313
/// <param name="valueFactory">Function that returns a value.</param>
14-
public abstract void Initialize(Func<object> valueFactory);
14+
protected internal abstract void Initialize(Func<object> valueFactory);
1515
}
1616
}

LazyProxy/LazyProxyBuilder.cs

+44-5
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,17 @@ 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);
28+
private static readonly MethodInfo CreateLazyMethod = typeof(LazyProxyImplementation)
29+
.GetMethod(nameof(LazyProxyImplementation.CreateInstance), BindingFlags.Public | BindingFlags.Static);
30+
31+
private static readonly MethodInfo DisposeLazyMethod = typeof(LazyProxyImplementation)
32+
.GetMethod(nameof(LazyProxyImplementation.DisposeInstance), BindingFlags.Public | BindingFlags.Static);
33+
34+
private static readonly Type DisposableInterface = typeof(IDisposable);
35+
36+
private static readonly MethodInfo DisposeMethod = DisposableInterface
37+
.GetMethod(nameof(IDisposable.Dispose), BindingFlags.Public | BindingFlags.Instance);
38+
3039

3140
/// <summary>
3241
/// Defines at runtime a class that implements interface T
@@ -89,7 +98,7 @@ public static object CreateInstance(Type type, Func<object> valueFactory)
8998
var proxyType = GetType(type);
9099

91100
// Using 'Initialize' method after the instance creation allows to improve performance
92-
// because Activator.CreateInstance executed with arguments is much slower.
101+
// because Activator.CreateInstance method performance is much worse with arguments.
93102
var instance = (LazyProxyBase) Activator.CreateInstance(proxyType);
94103
instance.Initialize(valueFactory);
95104

@@ -134,6 +143,7 @@ private static Type DefineProxyType(Type type)
134143
.AddServiceField(type, out var serviceField)
135144
.AddInitializeMethod(type, serviceField)
136145
.AddMethods(type, serviceField)
146+
.AddDisposeMethodIfNeeded(type, serviceField)
137147
.CreateTypeInfo();
138148
}
139149

@@ -167,8 +177,8 @@ private static TypeBuilder AddServiceField(this TypeBuilder typeBuilder,
167177
private static TypeBuilder AddInitializeMethod(this TypeBuilder typeBuilder, Type type, FieldInfo serviceField)
168178
{
169179
var methodBuilder = typeBuilder.DefineMethod(
170-
"Initialize",
171-
MethodAttributes.Public | MethodAttributes.Virtual,
180+
nameof(LazyProxyBase.Initialize),
181+
MethodAttributes.Family | MethodAttributes.Virtual,
172182
null,
173183
new [] { typeof(Func<object>) }
174184
);
@@ -229,13 +239,42 @@ private static TypeBuilder AddMethods(this TypeBuilder typeBuilder, Type type, F
229239
return typeBuilder;
230240
}
231241

242+
private static TypeBuilder AddDisposeMethodIfNeeded(this TypeBuilder typeBuilder, Type type, FieldInfo serviceField)
243+
{
244+
if (!DisposableInterface.IsAssignableFrom(type)) {
245+
return typeBuilder;
246+
}
247+
248+
var methodBuilder = typeBuilder.DefineMethod(
249+
DisposeMethod.Name,
250+
MethodAttributes.Public | MethodAttributes.Virtual,
251+
DisposeMethod.ReturnType,
252+
Array.Empty<Type>()
253+
);
254+
255+
var disposeLazyMethod = DisposeLazyMethod.MakeGenericMethod(type);
256+
257+
var generator = methodBuilder.GetILGenerator();
258+
259+
generator.Emit(OpCodes.Ldarg_0);
260+
generator.Emit(OpCodes.Ldfld, serviceField);
261+
generator.Emit(OpCodes.Call, disposeLazyMethod);
262+
generator.Emit(OpCodes.Ret);
263+
264+
typeBuilder.DefineMethodOverride(methodBuilder, DisposeMethod);
265+
266+
return typeBuilder;
267+
}
268+
232269
private static IEnumerable<MethodInfo> GetMethods(Type type)
233270
{
234271
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
235272

273+
var isDisposable = DisposableInterface.IsAssignableFrom(type);
236274
return type.GetMethods(flags)
237275
.Concat(type.GetInterfaces()
238276
.SelectMany(@interface => @interface.GetMethods(flags)))
277+
.Where(method => !isDisposable || method.Name != nameof(IDisposable.Dispose))
239278
.Distinct();
240279
}
241280

LazyProxy/LazyProxyImplementation.cs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
3+
namespace LazyProxy
4+
{
5+
/// <summary>
6+
/// This type hosts methods being used by lazy proxy implementations.
7+
/// </summary>
8+
public static class LazyProxyImplementation
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+
/// <summary>
22+
/// Disposes an instance owned by <see cref="Lazy{T}"/> if any.
23+
/// </summary>
24+
/// <param name="instanceOwner"><see cref="Lazy{T}"/> object.</param>
25+
/// <typeparam name="T">Type of lazy value. It must implement <see cref="IDisposable"/> interface.</typeparam>
26+
public static void DisposeInstance<T>(Lazy<T> instanceOwner)
27+
where T: IDisposable
28+
{
29+
if (instanceOwner.IsValueCreated)
30+
{
31+
instanceOwner.Value.Dispose();
32+
}
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)