To dispose all created singleton instances, simply dispose the composition instance. The composition itself becomes disposable as soon as it can create at least one disposable singleton, so using around it is the whole disposal strategy.
using Shouldly;
using Pure.DI;
using static Pure.DI.Lifetime;
DI.Setup(nameof(Composition))
// Disable Resolve methods to keep the public API minimal
.Hint(Hint.Resolve, "Off")
// A realistic example:
// a submarine has a shared hardware bus to onboard sensors.
// It should be created once and disposed when the "mission scope"
// (the composition instance) ends.
.Bind().As(Singleton).To<AcousticSensorBus>()
.Bind().To<SubmarineCombatSystem>()
.Root<ICombatSystem>("CombatSystem");
IAcousticSensorBus bus;
using (var composition = new Composition())
{
var combatSystem = composition.CombatSystem;
// Store the singleton instance to verify that it gets disposed
// when composition is disposed.
bus = combatSystem.SensorBus;
// In real usage you would call methods like:
// combatSystem.ScanForContacts();
}
// When the mission scope ends, all disposable singletons created by it
// must be disposed.
bus.IsDisposed.ShouldBeTrue();
interface IAcousticSensorBus
{
bool IsDisposed { get; }
}
// Represents a shared connection to submarine sensors (sonar, hydrophones, etc.).
// This is a singleton because the hardware bus is typically a single shared resource,
// and it must be cleaned up properly.
class AcousticSensorBus : IAcousticSensorBus, IDisposable
{
public bool IsDisposed { get; private set; }
public void Dispose() => IsDisposed = true;
}
interface ICombatSystem
{
IAcousticSensorBus SensorBus { get; }
}
// A "combat system" is a typical high-level service that uses shared hardware resources.
class SubmarineCombatSystem(IAcousticSensorBus sensorBus) : ICombatSystem
{
public IAcousticSensorBus SensorBus { get; } = sensorBus;
}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 runA composition class becomes disposable if it creates at least one disposable singleton instance.
This is the disposal path for process-wide resources a server or a desktop shell owns: message brokers, hardware handles, file writers, caches with a background timer. They are created lazily on first use and released when the host shuts the composition down.
Warning
Only what the composition created is disposed, and only in a reverse order of creation. Transient and PerResolve instances, and objects built up by a builder, are not in that list and stay the responsibility of their owner.
Exceptions thrown by one Dispose are caught and passed to the OnDisposeException partial method, so one faulty resource does not stop the rest. Override it to log, otherwise the failure is silent.
Prefer IAsyncDisposable (see Async disposable singleton) when a release must await I/O; a synchronous Dispose that blocks hides the cost in shutdown.
The following partial class will be generated
partial class Composition: IDisposable
{
#if NET9_0_OR_GREATER
private readonly Lock _lock = new Lock();
#else
private readonly Object _lock = new Object();
#endif
private object[] _disposables = new object[1];
private int _disposeIndex;
private AcousticSensorBus? _singletonWcfLikeChannel;
public ICombatSystem CombatSystem
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (_singletonWcfLikeChannel is null)
lock (_lock)
if (_singletonWcfLikeChannel is null)
{
_singletonWcfLikeChannel = new AcousticSensorBus();
_disposables[_disposeIndex++] = _singletonWcfLikeChannel;
}
return new SubmarineCombatSystem(_singletonWcfLikeChannel);
}
}
public void Dispose()
{
int disposeIndex;
object[] disposables;
lock (_lock)
{
disposeIndex = _disposeIndex;
_disposeIndex = 0;
disposables = _disposables;
_disposables = new object[1];
_singletonWcfLikeChannel = null;
}
while (disposeIndex-- > 0)
{
switch (disposables[disposeIndex])
{
case IDisposable disposableInstance:
try
{
disposableInstance.Dispose();
}
catch (Exception exception)
{
OnDisposeException(disposableInstance, exception);
}
break;
}
}
}
partial void OnDisposeException<T>(T disposableInstance, Exception exception) where T : IDisposable;
}Class diagram:
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
Composition --|> IDisposable
AcousticSensorBus --|> IAcousticSensorBus
SubmarineCombatSystem --|> ICombatSystem
Composition ..> SubmarineCombatSystem : ICombatSystem CombatSystem
SubmarineCombatSystem o-- "Singleton" AcousticSensorBus : IAcousticSensorBus
namespace Pure.DI.UsageTests.Lifetimes.DisposableSingletonScenario {
class AcousticSensorBus {
<<class>>
+AcousticSensorBus()
}
class Composition {
<<partial>>
+ICombatSystem CombatSystem
}
class IAcousticSensorBus {
<<interface>>
}
class ICombatSystem {
<<interface>>
}
class SubmarineCombatSystem {
<<class>>
+SubmarineCombatSystem(IAcousticSensorBus sensorBus)
}
}
namespace System {
class IDisposable {
<<interface>>
}
}
See also: