From e5a58bed198afc8541a55bb7309331ce33ff2ad3 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Thu, 21 Mar 2024 11:06:33 +0300 Subject: [PATCH] #44 Disposable Instances Handling --- rebuild.cmd | 4 + src/Pure.DI.Core/Components/Api.g.cs | 83 ++++++++++++++++++- src/Pure.DI.Core/Core/Code/Accumulator.cs | 1 + src/Pure.DI.Core/Core/Code/BuildTools.cs | 5 ++ .../Core/Code/VariablesBuilder.cs | 4 +- src/Pure.DI.Core/Features/Default.g.cs | 8 ++ .../AccumulatorTests.cs | 25 ++++-- .../TrackingDisposableInDelegatesScenario.cs | 9 +- .../Basics/TrackingDisposableScenario.cs | 13 ++- 9 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 rebuild.cmd diff --git a/rebuild.cmd b/rebuild.cmd new file mode 100644 index 000000000..e0a64f898 --- /dev/null +++ b/rebuild.cmd @@ -0,0 +1,4 @@ +for /d /r %%i in (obj) do @rmdir /S /Q "%%i" +for /d /r %%i in (bin) do @rmdir /S /Q "%%i" +dotnet build-server shutdown +dotnet build \ No newline at end of file diff --git a/src/Pure.DI.Core/Components/Api.g.cs b/src/Pure.DI.Core/Components/Api.g.cs index 3bbcac938..51907db56 100644 --- a/src/Pure.DI.Core/Components/Api.g.cs +++ b/src/Pure.DI.Core/Components/Api.g.cs @@ -1089,13 +1089,22 @@ internal enum Tag } /// - /// Gives the opportunity to collect disposable objects. + /// Gives the ability to manage disposable objects. /// - public partial class Owned : global::System.IDisposable + public interface IOwned : global::System.IDisposable + { + } + + /// + /// Gives the ability to manage disposable objects. + /// + [global::System.Diagnostics.DebuggerDisplay("{_disposables.Count} item(s)")] + [global::System.Diagnostics.DebuggerTypeProxy(typeof(global::Pure.DI.Owned.DebugView))] + internal partial class Owned : global::Pure.DI.IOwned { private global::System.Collections.Generic.List _disposables = new global::System.Collections.Generic.List(); - + /// /// Adds a disposable instance. /// @@ -1103,6 +1112,11 @@ public partial class Owned : global::System.IDisposable [global::System.Runtime.CompilerServices.MethodImpl((global::System.Runtime.CompilerServices.MethodImplOptions)256)] public void Add(global::System.IDisposable disposable) { + if (disposable is global::Pure.DI.IOwned) + { + return; + } + lock (_disposables) { _disposables.Add(disposable); @@ -1142,8 +1156,71 @@ public void Dispose() /// The actual type of instance being disposed of. partial void OnDisposeException(T disposableInstance, Exception exception) where T : global::System.IDisposable; + + private class DebugView + { + private readonly global::Pure.DI.Owned _owned; + + public DebugView(global::Pure.DI.Owned owned) + { + _owned = owned; + } + + [global::System.Diagnostics.DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public global::System.Collections.Generic.List Owns + { + get { return _owned._disposables; } + } + } } + /// + /// Contains a value and gives the ability to manage disposable objects. + /// + [global::System.Diagnostics.DebuggerDisplay("{Value}")] + [global::System.Diagnostics.DebuggerTypeProxy(typeof(global::Pure.DI.Owned<>.DebugView))] + public readonly struct Owned : global::Pure.DI.IOwned + { + /// + /// The value. + /// + public readonly T Value; + private readonly IOwned _owned; + + public Owned(T value, IOwned owned) + { + Value = value; + _owned = owned; + } + + /// + public void Dispose() + { + _owned.Dispose(); + } + + private class DebugView + { + private readonly global::Pure.DI.Owned _owned; + + public DebugView(global::Pure.DI.Owned owned) + { + _owned = owned; + } + + public T Value + { + get { return _owned.Value; } + } + + [global::System.Diagnostics.DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] + public global::Pure.DI.IOwned Owned + { + get { return _owned._owned; } + } + } + } + /// /// An API for a Dependency Injection setup. /// diff --git a/src/Pure.DI.Core/Core/Code/Accumulator.cs b/src/Pure.DI.Core/Core/Code/Accumulator.cs index 36234f194..ab3b13a0a 100644 --- a/src/Pure.DI.Core/Core/Code/Accumulator.cs +++ b/src/Pure.DI.Core/Core/Code/Accumulator.cs @@ -1,6 +1,7 @@ namespace Pure.DI.Core.Code; internal record Accumulator( + bool IsRoot, string Name, bool IsDeclared, ITypeSymbol Type, diff --git a/src/Pure.DI.Core/Core/Code/BuildTools.cs b/src/Pure.DI.Core/Core/Code/BuildTools.cs index 2d30f38e8..26c623426 100644 --- a/src/Pure.DI.Core/Core/Code/BuildTools.cs +++ b/src/Pure.DI.Core/Core/Code/BuildTools.cs @@ -52,6 +52,7 @@ public IEnumerable OnCreated(BuildContext ctx, Variable variable) .ToImmutableHashSet(SymbolEqualityComparer.Default); var lines = ctx.Accumulators + .Where(i => FilterAccumulator(i, variable.Node.Lifetime)) .Where(i => baseTypes.Contains(i.Type)) .Select(i => new Line(0, $"{i.Name}.Add({variable.VariableName});")) .ToList(); @@ -75,6 +76,10 @@ public IEnumerable OnCreated(BuildContext ctx, Variable variable) return lines; } + private static bool FilterAccumulator(Accumulator accumulator, Lifetime lifetime) => + accumulator.IsRoot + || lifetime is not (Lifetime.Singleton or Lifetime.Scoped or Lifetime.PerResolve); + private static object? GetTag(BuildContext ctx, Variable variable) { var tag = variable.Injection.Tag; diff --git a/src/Pure.DI.Core/Core/Code/VariablesBuilder.cs b/src/Pure.DI.Core/Core/Code/VariablesBuilder.cs index be4b02fd1..e82634b71 100644 --- a/src/Pure.DI.Core/Core/Code/VariablesBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/VariablesBuilder.cs @@ -50,6 +50,7 @@ public Block Build( var pathIds = new HashSet(); var hasLazy = false; ICollection? accumulators = default; + var isRoot = true; foreach (var pathItem in currentStatement.GetPath()) { var pathVar = pathItem.Current; @@ -66,12 +67,13 @@ public Block Build( } accumulators = pathVar.Node.Accumulators; + isRoot = false; } accumulators ??= rootNode.Accumulators; if (isAccumulator) { - accumulators.Add(new Accumulator(GetAccumulatorName(variable), false, construct.ElementType, construct.Type)); + accumulators.Add(new Accumulator(isRoot, GetAccumulatorName(variable), false, construct.ElementType, construct.Type)); } foreach (var (isDepResolved, depNode, depInjection, _) in dependencies) diff --git a/src/Pure.DI.Core/Features/Default.g.cs b/src/Pure.DI.Core/Features/Default.g.cs index e9b0e8233..7da6bf793 100644 --- a/src/Pure.DI.Core/Features/Default.g.cs +++ b/src/Pure.DI.Core/Features/Default.g.cs @@ -16,6 +16,14 @@ private static void Setup() .Accumulate(Lifetime.Transient) .Accumulate(Lifetime.PerResolve) .Accumulate(Lifetime.PerBlock) + .Bind().To() + .Bind>() + .As(Lifetime.PerBlock) + .To(ctx => { + ctx.Inject(out var owned); + ctx.Inject(ctx.Tag, out var value); + return new Owned(value, owned); + }) .Bind>() .As(Lifetime.PerResolve) .To(ctx => new global::System.Func(() => diff --git a/tests/Pure.DI.IntegrationTests/AccumulatorTests.cs b/tests/Pure.DI.IntegrationTests/AccumulatorTests.cs index 5dfa6f2f2..d4fb86399 100644 --- a/tests/Pure.DI.IntegrationTests/AccumulatorTests.cs +++ b/tests/Pure.DI.IntegrationTests/AccumulatorTests.cs @@ -95,11 +95,12 @@ class CardboardBox : IBox public CardboardBox(Func<(T value, Accumulator acc)> contentFactory) { var content = contentFactory(); - foreach(var dep in content.acc) + foreach(var dep in content.acc.Items) { Console.WriteLine(dep); } + Console.WriteLine("CardboardBox created"); Content = content.value; } @@ -117,11 +118,16 @@ class ShroedingersCat : ICat // The decoherence of the superposition at the time of observation via an irreversible process public State State => _superposition.Value; - - public override string ToString() => $"{State} cat"; } - class Accumulator: List { } + class Accumulator + { + private readonly List _items = new List(); + + public IEnumerable Items =>_items.ToArray(); + + public void Add(object item) => _items.Add(item); + } // Let's glue all together @@ -145,7 +151,7 @@ private static void Setup() // Represents a cardboard box with any content .Bind>().To>() // Composition Root - .Root<(Program program, Accumulator)>("Root"); + .Root<(Program program, Accumulator acc)>("Root"); } } @@ -159,6 +165,13 @@ public static void Main() { var composition = new Composition(); var root = composition.Root; + Console.WriteLine(root); + foreach(var dep in root.acc.Items) + { + Console.WriteLine(dep); + } + + Console.WriteLine("Program created"); } } } @@ -171,6 +184,6 @@ public static void Main() // Then result.Success.ShouldBeTrue(result); - result.StdOut.Length.ShouldBe(3); + result.StdOut.ShouldBe(ImmutableArray.Create("Value is not created.", "Sample.ShroedingersCat", "(Sample.ShroedingersCat, Sample.Accumulator)", "CardboardBox created", "(Sample.Program, Sample.Accumulator)", "System.Func`1[System.ValueTuple`2[Sample.ICat,Sample.Accumulator]]", "[Sample.ShroedingersCat]", "Sample.Program", "(Sample.Program, Sample.Accumulator)", "Program created")); } } \ No newline at end of file diff --git a/tests/Pure.DI.UsageTests/Basics/TrackingDisposableInDelegatesScenario.cs b/tests/Pure.DI.UsageTests/Basics/TrackingDisposableInDelegatesScenario.cs index e9220e16f..fe41607f9 100644 --- a/tests/Pure.DI.UsageTests/Basics/TrackingDisposableInDelegatesScenario.cs +++ b/tests/Pure.DI.UsageTests/Basics/TrackingDisposableInDelegatesScenario.cs @@ -30,14 +30,14 @@ interface IService public IDependency Dependency { get; } } -class Service(Func<(IDependency dependency, Owned owned)> dependencyFactory) +class Service(Func> dependencyFactory) : IService, IDisposable { - private readonly (IDependency value, Owned owned) _dependency = dependencyFactory(); + private readonly Owned _dependency = dependencyFactory(); - public IDependency Dependency => _dependency.value; + public IDependency Dependency => _dependency.Value; - public void Dispose() => _dependency.owned.Dispose(); + public void Dispose() => _dependency.Dispose(); } partial class Composition @@ -57,7 +57,6 @@ public void Run() { // { var composition = new Composition(); - var root1 = composition.Root; var root2 = composition.Root; diff --git a/tests/Pure.DI.UsageTests/Basics/TrackingDisposableScenario.cs b/tests/Pure.DI.UsageTests/Basics/TrackingDisposableScenario.cs index b2d88d52a..9e97d1ee3 100644 --- a/tests/Pure.DI.UsageTests/Basics/TrackingDisposableScenario.cs +++ b/tests/Pure.DI.UsageTests/Basics/TrackingDisposableScenario.cs @@ -41,7 +41,7 @@ private void Setup() => DI.Setup(nameof(Composition)) .Bind().To() .Bind().To() - .Root<(IService service, Owned owned)>("Root"); + .Root>("Root"); } // } @@ -52,25 +52,24 @@ public void Run() { // { var composition = new Composition(); - var root1 = composition.Root; var root2 = composition.Root; - root2.owned.Dispose(); + root2.Dispose(); // Checks that the disposable instances // associated with root1 have been disposed of - root2.service.Dependency.IsDisposed.ShouldBeTrue(); + root2.Value.Dependency.IsDisposed.ShouldBeTrue(); // Checks that the disposable instances // associated with root2 have not been disposed of - root1.service.Dependency.IsDisposed.ShouldBeFalse(); + root1.Value.Dependency.IsDisposed.ShouldBeFalse(); - root1.owned.Dispose(); + root1.Dispose(); // Checks that the disposable instances // associated with root2 have been disposed of - root1.service.Dependency.IsDisposed.ShouldBeTrue(); + root1.Value.Dependency.IsDisposed.ShouldBeTrue(); // } new Composition().SaveClassDiagram(); }