Demonstrates how async disposable instances are tracked per composition root and disposed asynchronously when the composition is disposed.
using Shouldly;
using Pure.DI;
var composition = new Composition();
// Creates two independent roots (queries), each with its own dependency graph
var query1 = composition.Query;
var query2 = composition.Query;
// Disposes of the second query
await query2.DisposeAsync();
// Checks that the connection associated with the second query has been closed
query2.Value.Connection.IsDisposed.ShouldBeTrue();
// At the same time, the connection of the first query remains active
query1.Value.Connection.IsDisposed.ShouldBeFalse();
// Disposes of the first query
await query1.DisposeAsync();
// Now the first connection is also closed
query1.Value.Connection.IsDisposed.ShouldBeTrue();
// Interface for a resource requiring asynchronous disposal (e.g., DB)
interface IDbConnection
{
bool IsDisposed { get; }
}
class DbConnection : IDbConnection, IAsyncDisposable
{
public bool IsDisposed { get; private set; }
public ValueTask DisposeAsync()
{
IsDisposed = true;
return ValueTask.CompletedTask;
}
}
interface IQuery
{
public IDbConnection Connection { get; }
}
class Query(IDbConnection connection) : IQuery
{
public IDbConnection Connection { get; } = connection;
}
partial class Composition
{
static void Setup() =>
DI.Setup()
.Bind().To<DbConnection>()
.Bind().To<Query>()
// A special composition root 'Owned' that allows
// managing the lifetime of IQuery and its dependencies
.Root<Owned<IQuery>>("Query");
}Running this code sample locally
- Make sure you have the .NET SDK 10.0 or later installed
dotnet --list-sdk- Create a net10.0 (or later) console application
dotnet new console -n Sampledotnet add package Pure.DI
dotnet add package Shouldly- Copy the example code into the Program.cs file
You are ready to run the example 🚀
dotnet runNote
Async disposable tracking ensures proper async cleanup of all disposable instances within a composition scope.
The following partial class will be generated:
partial class Composition
{
#if NET9_0_OR_GREATER
private readonly Lock _lock = new Lock();
#else
private readonly Object _lock = new Object();
#endif
public Owned<IQuery> Query
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
var perBlockOwned156 = new Owned();
Owned<IQuery> perBlockOwned155;
// Creates the owner of an instance
Owned transientOwned157;
Owned localOwned3 = perBlockOwned156;
transientOwned157 = localOwned3;
lock (_lock)
{
perBlockOwned156.Add(transientOwned157);
}
IOwned localOwned2 = transientOwned157;
var transientDbConnection159 = new DbConnection();
lock (_lock)
{
perBlockOwned156.Add(transientDbConnection159);
}
IQuery localValue7 = new Query(transientDbConnection159);
perBlockOwned155 = new Owned<IQuery>(localValue7, localOwned2);
lock (_lock)
{
perBlockOwned156.Add(perBlockOwned155);
}
return perBlockOwned155;
}
}
}Class diagram:
---
config:
maxTextSize: 2147483647
maxEdges: 2147483647
class:
hideEmptyMembersBox: true
---
classDiagram
Owned --|> IOwned
DbConnection --|> IDbConnection
DbConnection --|> IAsyncDisposable
Query --|> IQuery
Composition ..> OwnedᐸIQueryᐳ : OwnedᐸIQueryᐳ Query
Query *-- DbConnection : IDbConnection
OwnedᐸIQueryᐳ *-- Owned : IOwned
OwnedᐸIQueryᐳ *-- Query : IQuery
namespace Pure.DI {
class IOwned {
<<interface>>
}
class Owned {
<<class>>
}
class OwnedᐸIQueryᐳ {
<<struct>>
}
}
namespace Pure.DI.UsageTests.Advanced.TrackingAsyncDisposableScenario {
class Composition {
<<partial>>
+OwnedᐸIQueryᐳ Query
}
class DbConnection {
<<class>>
+DbConnection()
}
class IDbConnection {
<<interface>>
}
class IQuery {
<<interface>>
}
class Query {
<<class>>
+Query(IDbConnection connection)
}
}
namespace System {
class IAsyncDisposable {
<<interface>>
}
}