A comprehensive .NET library providing useful `IDisposable` and `IAsyncDisposable` implementations for common resource management patterns.
$ dotnet add package NCode.DisposablesA comprehensive .NET library providing useful IDisposable and IAsyncDisposable implementations for common resource management patterns.
SynchronizationContext (e.g., UI thread)All implementations have async counterparts (IAsyncDisposable):
AsyncDisposable.Empty, Create(), Aggregate(), Collection(), Shared()AsyncDisposableAdapter - Wraps IDisposable as IAsyncDisposableDisposeAsyncIfAvailable() - Calls DisposeAsync if available, otherwise DisposeDisposeAll() / DisposeAllAsync() - Dispose all items in a collection (LIFO order)AsSharedReference() - Convert a disposable to a shared reference with leasing| Feature | Sync API | Async API |
|---|---|---|
| Empty (no-op) | Disposable.Empty | AsyncDisposable.Empty |
| Action callback | Disposable.Create(action) | AsyncDisposable.Create(func) |
| Aggregate wrapper | Disposable.Aggregate(disposable) | AsyncDisposable.Aggregate(disposable) |
| Collection | Disposable.Collection(items) | AsyncDisposable.Collection(items) |
| Shared reference | SharedReference.Create(value) | AsyncSharedReference.Create(value) |
| Context disposal | Disposable.Context(disposable, ctx) | — |
| Adapt sync→async | — | AsyncDisposable.Adapt(disposable) |
// Singleton instance - useful as default/placeholder
IDisposable disposable = Disposable.Empty;
disposable.Dispose(); // no-op
// Execute cleanup logic on dispose (idempotent - runs only once)
IDisposable disposable = Disposable.Create(() => Console.WriteLine("Disposed!"));
disposable.Dispose(); // prints "Disposed!"
disposable.Dispose(); // no-op
// Wrapper allowing the underlying resource to be swapped
var aggregate = Disposable.Aggregate(initialResource);
aggregate.Disposable = newResource; // swap resource
aggregate.Dispose(); // disposes current resource
// Manage multiple disposables - disposed in reverse (LIFO) order
var collection = Disposable.Collection(resource1, resource2, resource3);
collection.Add(resource4);
collection.Remove(resource2); // removed items are NOT disposed
collection.Dispose(); // disposes: resource4, resource3, resource1
// Share a resource with reference counting
IDisposable resource = CreateExpensiveResource();
var lease1 = SharedReference.Create(resource);
var lease2 = lease1.AddReference();
var lease3 = lease2.AddReference();
lease1.Value.DoWork();
lease1.Dispose();
lease2.Value.DoWork();
lease2.Dispose();
lease3.Value.DoWork();
lease3.Dispose(); // resource disposed here (ref count = 0)
// Dispose on a specific SynchronizationContext (e.g., UI thread)
var context = SynchronizationContext.Current;
var disposable = Disposable.Context(uiResource, context, async: false);
disposable.Dispose(); // runs on the context's thread
// Wrap IDisposable as IAsyncDisposable
IDisposable syncResource = CreateResource();
IAsyncDisposable asyncResource = AsyncDisposable.Adapt(syncResource);
await asyncResource.DisposeAsync();
// Dispose async if available, otherwise sync
await disposable.DisposeAsyncIfAvailable();
// Dispose all items in a collection (LIFO order)
var items = new object[] { resource1, resource2, nonDisposable, resource3 };
items.DisposeAll(); // disposes only IDisposable items, in reverse order
// Convert to shared reference
using var lease = myDisposable.AsSharedReference();
Please provide any feedback, comments, or issues to this GitHub project here.
idempotent option to certain methods.idempotent option to use function overloads.