Using Declarations and Disposable Patterns in C#
Resource management is a core concern in any language that interacts with the operating system. File handles, database connections, HTTP clients, and network sockets all need deterministic cleanup. C# provides the IDisposable pattern and the using keyword to handle this, and C# 8 simplified the syntax further with using declarations.
Using Declarations vs Using Statements
The traditional using statement creates a block that disposes the resource at the closing brace:
using (var stream = new FileStream("data.bin", FileMode.Open))
{
// stream is available here
var buffer = new byte[1024];
await stream.ReadAsync(buffer);
} // stream.Dispose() called here
A using declaration, introduced in C# 8, removes the block. The resource is disposed at the end of the enclosing scope:
using var stream = new FileStream("data.bin", FileMode.Open);
var buffer = new byte[1024];
await stream.ReadAsync(buffer);
// stream.Dispose() called when the method exits
Using declarations reduce nesting and are preferred in most scenarios. Use the traditional block form when you need the resource disposed before the method ends:
string content;
using (var reader = new StreamReader("file.txt"))
{
content = await reader.ReadToEndAsync();
} // reader is disposed here
// Continue working with 'content' — the file handle is already released
await ProcessContent(content);
Implementing IDisposable
When your class owns unmanaged resources or disposable objects, implement IDisposable:
public class DataProcessor : IDisposable
{
private readonly FileStream _stream;
private bool _disposed;
public DataProcessor(string path)
{
_stream = new FileStream(path, FileMode.Open);
}
public void Process()
{
ObjectDisposedException.ThrowIf(_disposed, this);
// Use _stream
}
public void Dispose()
{
if (_disposed) return;
_stream.Dispose();
_disposed = true;
}
}
For classes that directly hold unmanaged resources (rare in modern .NET), use the full dispose pattern with a finaliser:
public class UnmanagedWrapper : IDisposable
{
private IntPtr _handle;
private bool _disposed;
public UnmanagedWrapper()
{
_handle = NativeMethods.CreateHandle();
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
// Dispose managed resources
}
// Release unmanaged resources
if (_handle != IntPtr.Zero)
{
NativeMethods.CloseHandle(_handle);
_handle = IntPtr.Zero;
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~UnmanagedWrapper() => Dispose(false);
}
Most classes only own managed disposable resources and do not need a finaliser. The simpler pattern above is sufficient.
IAsyncDisposable
For resources that require asynchronous cleanup — such as flushing data to a network stream — implement IAsyncDisposable:
public class AsyncDataWriter : IAsyncDisposable
{
private readonly StreamWriter _writer;
public AsyncDataWriter(string path)
{
_writer = new StreamWriter(path);
}
public async Task WriteAsync(string data)
{
await _writer.WriteLineAsync(data);
}
public async ValueTask DisposeAsync()
{
await _writer.FlushAsync();
await _writer.DisposeAsync();
}
}
Use await using with async disposable types:
await using var writer = new AsyncDataWriter("output.txt");
await writer.WriteAsync("Hello, world!");
// DisposeAsync() is awaited when the scope ends
Stacking Multiple Disposables
Using declarations stack naturally:
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
using var command = connection.CreateCommand();
command.CommandText = "SELECT * FROM Users";
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
Console.WriteLine(reader.GetString(0));
}
// All three are disposed in reverse order: reader, command, connection
Resources are disposed in reverse declaration order, which matches the natural dependency chain.
Common Pitfalls
Returning a disposable from a method:
// Bug: the stream is disposed before the caller can use it
public Stream GetStream()
{
using var stream = new MemoryStream();
return stream; // Disposed immediately!
}
// Correct: the caller takes ownership
public Stream GetStream()
{
return new MemoryStream(); // Caller must dispose
}
Disposing shared resources:
// Bug: HttpClient should be long-lived and shared
using var client = new HttpClient(); // Do not create per-request
// Correct: use IHttpClientFactory or a static instance
Swallowing exceptions in Dispose:
If both the body and Dispose throw exceptions, the body's exception is lost. Keep Dispose implementations simple and non-throwing where possible.
Modern Best Practice
In contemporary C# code, using declarations are the default. They reduce nesting, improve readability, and work correctly with both synchronous and asynchronous disposal. When writing new disposable types, prefer implementing only IAsyncDisposable if the cleanup is inherently asynchronous, or both interfaces if callers may use the type in synchronous contexts.