Skip to content

Commit

Permalink
#44 Disposable Instances Handling
Browse files Browse the repository at this point in the history
  • Loading branch information
NikolayPianikov committed Mar 21, 2024
1 parent f35338d commit e5a58be
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 22 deletions.
4 changes: 4 additions & 0 deletions rebuild.cmd
Original file line number Diff line number Diff line change
@@ -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
83 changes: 80 additions & 3 deletions src/Pure.DI.Core/Components/Api.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1089,20 +1089,34 @@ internal enum Tag
}

/// <summary>
/// Gives the opportunity to collect disposable objects.
/// Gives the ability to manage disposable objects.
/// </summary>
public partial class Owned : global::System.IDisposable
public interface IOwned : global::System.IDisposable
{
}

/// <summary>
/// Gives the ability to manage disposable objects.
/// </summary>
[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<global::System.IDisposable> _disposables
= new global::System.Collections.Generic.List<global::System.IDisposable>();

/// <summary>
/// Adds a disposable instance.
/// </summary>
/// <param name="disposable">The disposable instance.</param>
[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);
Expand Down Expand Up @@ -1142,8 +1156,71 @@ public void Dispose()
/// <typeparam name="T">The actual type of instance being disposed of.</typeparam>
partial void OnDisposeException<T>(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<global::System.IDisposable> Owns
{
get { return _owned._disposables; }
}
}
}

/// <summary>
/// Contains a value and gives the ability to manage disposable objects.
/// </summary>
[global::System.Diagnostics.DebuggerDisplay("{Value}")]
[global::System.Diagnostics.DebuggerTypeProxy(typeof(global::Pure.DI.Owned<>.DebugView))]
public readonly struct Owned<T> : global::Pure.DI.IOwned
{
/// <summary>
/// The value.
/// </summary>
public readonly T Value;
private readonly IOwned _owned;

public Owned(T value, IOwned owned)
{
Value = value;
_owned = owned;
}

/// <inheritdoc />
public void Dispose()
{
_owned.Dispose();
}

private class DebugView
{
private readonly global::Pure.DI.Owned<T> _owned;

public DebugView(global::Pure.DI.Owned<T> 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; }
}
}
}

/// <summary>
/// An API for a Dependency Injection setup.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/Pure.DI.Core/Core/Code/Accumulator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace Pure.DI.Core.Code;

internal record Accumulator(
bool IsRoot,
string Name,
bool IsDeclared,
ITypeSymbol Type,
Expand Down
5 changes: 5 additions & 0 deletions src/Pure.DI.Core/Core/Code/BuildTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public IEnumerable<Line> 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();
Expand All @@ -75,6 +76,10 @@ public IEnumerable<Line> 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;
Expand Down
4 changes: 3 additions & 1 deletion src/Pure.DI.Core/Core/Code/VariablesBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public Block Build(
var pathIds = new HashSet<int>();
var hasLazy = false;
ICollection<Accumulator>? accumulators = default;
var isRoot = true;
foreach (var pathItem in currentStatement.GetPath())
{
var pathVar = pathItem.Current;
Expand All @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions src/Pure.DI.Core/Features/Default.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ private static void Setup()
.Accumulate<global::System.IDisposable, global::Pure.DI.Owned>(Lifetime.Transient)
.Accumulate<global::System.IDisposable, global::Pure.DI.Owned>(Lifetime.PerResolve)
.Accumulate<global::System.IDisposable, global::Pure.DI.Owned>(Lifetime.PerBlock)
.Bind<global::Pure.DI.IOwned>().To<Owned>()
.Bind<global::Pure.DI.Owned<TT>>()
.As(Lifetime.PerBlock)
.To(ctx => {
ctx.Inject<Owned>(out var owned);
ctx.Inject<TT>(ctx.Tag, out var value);
return new Owned<TT>(value, owned);
})
.Bind<global::System.Func<TT>>()
.As(Lifetime.PerResolve)
.To(ctx => new global::System.Func<TT>(() =>
Expand Down
25 changes: 19 additions & 6 deletions tests/Pure.DI.IntegrationTests/AccumulatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,12 @@ class CardboardBox<T> : IBox<T>
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;
}
Expand All @@ -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<object> { }
class Accumulator
{
private readonly List<object> _items = new List<object>();
public IEnumerable<object> Items =>_items.ToArray();
public void Add(object item) => _items.Add(item);
}
// Let's glue all together
Expand All @@ -145,7 +151,7 @@ private static void Setup()
// Represents a cardboard box with any content
.Bind<IBox<TT>>().To<CardboardBox<TT>>()
// Composition Root
.Root<(Program program, Accumulator)>("Root");
.Root<(Program program, Accumulator acc)>("Root");
}
}
Expand All @@ -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");
}
}
}
Expand All @@ -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"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ interface IService
public IDependency Dependency { get; }
}

class Service(Func<(IDependency dependency, Owned owned)> dependencyFactory)
class Service(Func<Owned<IDependency>> dependencyFactory)
: IService, IDisposable
{
private readonly (IDependency value, Owned owned) _dependency = dependencyFactory();
private readonly Owned<IDependency> _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
Expand All @@ -57,7 +57,6 @@ public void Run()
{
// {
var composition = new Composition();

var root1 = composition.Root;
var root2 = composition.Root;

Expand Down
13 changes: 6 additions & 7 deletions tests/Pure.DI.UsageTests/Basics/TrackingDisposableScenario.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private void Setup() =>
DI.Setup(nameof(Composition))
.Bind<IDependency>().To<Dependency>()
.Bind<IService>().To<Service>()
.Root<(IService service, Owned owned)>("Root");
.Root<Owned<IService>>("Root");
}
// }

Expand All @@ -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();
}
Expand Down

0 comments on commit e5a58be

Please sign in to comment.