Skip to content

Commit ad0c6ac

Browse files
Transient instances now disposed and collected within constructing container
1 parent e33bb02 commit ad0c6ac

7 files changed

+105
-14
lines changed

Assets/Reflex.EditModeTests/GarbageCollectionTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ private class Service
1414
{
1515
}
1616

17-
private static void ForceGarbageCollection()
17+
public static void ForceGarbageCollection()
1818
{
1919
Resources.UnloadUnusedAssets();
2020
GC.Collect();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using System;
2+
using FluentAssertions;
3+
using NUnit.Framework;
4+
using Reflex.Core;
5+
6+
namespace Reflex.EditModeTests
7+
{
8+
public class TransientTests
9+
{
10+
private class Service : IDisposable
11+
{
12+
public bool IsDisposed { get; private set; }
13+
14+
public void Dispose()
15+
{
16+
IsDisposed = true;
17+
}
18+
}
19+
20+
[Test]
21+
public void TransientFromType_ConstructedInstances_ShouldBeDisposed_WithinConstructingContainer()
22+
{
23+
var parentContainer = new ContainerBuilder().AddTransient(typeof(Service)).Build();
24+
var childContainer = parentContainer.Scope();
25+
26+
var instanceConstructedByChild = childContainer.Resolve<Service>();
27+
var instanceConstructedByParent = parentContainer.Resolve<Service>();
28+
29+
childContainer.Dispose();
30+
31+
instanceConstructedByChild.IsDisposed.Should().BeTrue();
32+
instanceConstructedByParent.IsDisposed.Should().BeFalse();
33+
}
34+
35+
[Test]
36+
public void TransientFromFactory_ConstructedInstances_ShouldBeDisposed_WithinConstructingContainer()
37+
{
38+
var parentContainer = new ContainerBuilder().AddTransient(_ => new Service()).Build();
39+
var childContainer = parentContainer.Scope();
40+
41+
var instanceConstructedByChild = childContainer.Resolve<Service>();
42+
var instanceConstructedByParent = parentContainer.Resolve<Service>();
43+
44+
childContainer.Dispose();
45+
46+
instanceConstructedByChild.IsDisposed.Should().BeTrue();
47+
instanceConstructedByParent.IsDisposed.Should().BeFalse();
48+
}
49+
50+
[Test, Retry(3)]
51+
public void TransientFromType_ConstructedInstances_ShouldBeCollected_WhenConstructingContainerIsDisposed()
52+
{
53+
WeakReference instanceConstructedByChild;
54+
WeakReference instanceConstructedByParent;
55+
var parentContainer = new ContainerBuilder().AddTransient(typeof(Service)).Build();
56+
57+
void Act()
58+
{
59+
using (var childContainer = parentContainer.Scope())
60+
{
61+
instanceConstructedByChild = new WeakReference(childContainer.Resolve<Service>());
62+
instanceConstructedByParent = new WeakReference(parentContainer.Resolve<Service>());
63+
}
64+
}
65+
66+
Act();
67+
GarbageCollectionTests.ForceGarbageCollection();
68+
instanceConstructedByChild.IsAlive.Should().BeFalse();
69+
instanceConstructedByParent.IsAlive.Should().BeTrue();
70+
}
71+
72+
[Test, Retry(3)]
73+
public void TransientFromFactory_ConstructedInstances_ShouldBeCollected_WhenConstructingContainerIsDisposed()
74+
{
75+
WeakReference instanceConstructedByChild;
76+
WeakReference instanceConstructedByParent;
77+
var parentContainer = new ContainerBuilder().AddTransient(c => new Service()).Build();
78+
79+
void Act()
80+
{
81+
using (var childContainer = parentContainer.Scope())
82+
{
83+
instanceConstructedByChild = new WeakReference(childContainer.Resolve<Service>());
84+
instanceConstructedByParent = new WeakReference(parentContainer.Resolve<Service>());
85+
}
86+
}
87+
88+
Act();
89+
GarbageCollectionTests.ForceGarbageCollection();
90+
instanceConstructedByChild.IsAlive.Should().BeFalse();
91+
instanceConstructedByParent.IsAlive.Should().BeTrue();
92+
}
93+
}
94+
}

Assets/Reflex.EditModeTests/TransientTests.cs.meta

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/Reflex/Core/Container.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ namespace Reflex.Core
1212
{
1313
public sealed class Container : IDisposable
1414
{
15-
private readonly DisposableCollection _disposables;
1615

1716
public string Name { get; }
1817
internal Container Parent { get; }
1918
internal List<Container> Children { get; } = new();
2019
internal Dictionary<Type, List<IResolver>> ResolversByContract { get; }
20+
internal DisposableCollection Disposables { get; }
2121

2222
internal Container(string name, Container parent, Dictionary<Type, List<IResolver>> resolversByContract, DisposableCollection disposables)
2323
{
@@ -26,7 +26,7 @@ internal Container(string name, Container parent, Dictionary<Type, List<IResolve
2626
Parent = parent;
2727
Parent?.Children.Add(this);
2828
ResolversByContract = resolversByContract;
29-
_disposables = disposables;
29+
Disposables = disposables;
3030
OverrideSelfInjection();
3131
}
3232

@@ -49,7 +49,7 @@ public void Dispose()
4949

5050
Parent?.Children.Remove(this);
5151
ResolversByContract.Clear();
52-
_disposables.Dispose();
52+
Disposables.Dispose();
5353
ReflexLogger.Log($"Container {Name} disposed", LogLevel.Info);
5454
}
5555

Assets/Reflex/Core/ContainerBuilder.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public Container Build()
1919
var disposables = new DisposableCollection();
2020
var resolversByContract = new Dictionary<Type, List<IResolver>>();
2121

22-
// Inherit parent resolvers
22+
// Inherited resolvers
2323
if (Parent != null)
2424
{
2525
foreach (var kvp in Parent.ResolversByContract)
@@ -28,7 +28,7 @@ public Container Build()
2828
}
2929
}
3030

31-
// Owned Resolvers
31+
// Owned resolvers
3232
foreach (var binding in Bindings)
3333
{
3434
disposables.Add(binding.Resolver);

Assets/Reflex/Resolvers/TransientFactoryResolver.cs

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
using System;
22
using Reflex.Core;
33
using Reflex.Enums;
4-
using Reflex.Generics;
54

65
namespace Reflex.Resolvers
76
{
87
internal sealed class TransientFactoryResolver : IResolver
98
{
109
private readonly Func<Container, object> _factory;
11-
private readonly DisposableCollection _disposables = new();
1210
public Lifetime Lifetime => Lifetime.Transient;
1311

1412
public TransientFactoryResolver(Func<Container, object> factory)
@@ -21,14 +19,13 @@ public object Resolve(Container container)
2119
{
2220
Diagnosis.IncrementResolutions(this);
2321
var instance = _factory.Invoke(container);
24-
_disposables.TryAdd(instance);
22+
container.Disposables.TryAdd(instance);
2523
Diagnosis.RegisterInstance(this, instance);
2624
return instance;
2725
}
2826

2927
public void Dispose()
3028
{
31-
_disposables.Dispose();
3229
}
3330
}
3431
}

Assets/Reflex/Resolvers/TransientTypeResolver.cs

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
using System;
22
using Reflex.Core;
33
using Reflex.Enums;
4-
using Reflex.Generics;
54

65
namespace Reflex.Resolvers
76
{
87
internal sealed class TransientTypeResolver : IResolver
98
{
109
private readonly Type _concreteType;
11-
private readonly DisposableCollection _disposables = new();
1210
public Lifetime Lifetime => Lifetime.Transient;
1311

1412
public TransientTypeResolver(Type concreteType)
@@ -21,14 +19,13 @@ public object Resolve(Container container)
2119
{
2220
Diagnosis.IncrementResolutions(this);
2321
var instance = container.Construct(_concreteType);
24-
_disposables.TryAdd(instance);
22+
container.Disposables.TryAdd(instance);
2523
Diagnosis.RegisterInstance(this, instance);
2624
return instance;
2725
}
2826

2927
public void Dispose()
3028
{
31-
_disposables.Dispose();
3229
}
3330
}
3431
}

0 commit comments

Comments
 (0)