.NETMedium
IDisposable vs IAsyncDisposable in C# — when each one matters
Both signal deterministic cleanup, but IAsyncDisposable.DisposeAsync() lets cleanup await asynchronous work without blocking a thread.
When to pick which
| Type owns | Implement |
|---|---|
| Unmanaged handle, file, socket (sync close) | IDisposable |
| Async network resource (DB connection, gRPC channel, stream that flushes over the wire) | IAsyncDisposable |
| Both (pooled DB connection) | Both — and call the async one when possible |
Correct usage
// Synchronous
using (var fs = File.OpenRead(path)) { ... }
// Async (C# 8+)
await using (var conn = new SqlConnection(cs))
{
await conn.OpenAsync();
...
} // DisposeAsync() awaited here
Implementing both
public class HybridResource : IDisposable, IAsyncDisposable
{
private Stream? _stream;
public async ValueTask DisposeAsync()
{
if (_stream is not null)
{
await _stream.DisposeAsync();
_stream = null;
}
GC.SuppressFinalize(this);
}
public void Dispose()
{
_stream?.Dispose();
_stream = null;
GC.SuppressFinalize(this);
}
}
Gotcha — sync-over-async deadlock
If you call Dispose() (sync) on a class whose real cleanup is async, the implementation often does DisposeAsync().AsTask().GetAwaiter().GetResult(). On a SynchronizationContext-bound thread (legacy ASP.NET, WPF) this deadlocks. Always prefer await using in async methods.
When to use neither
Pure managed objects don't need either — let the GC handle them. Implementing IDisposable on a class with no unmanaged resources is a common over-engineering signal.