Lesson 25: IDisposable, Finalizers & Resource Management
Learn to manage unmanaged resources correctly — implement IDisposable, use using statements, and avoid resource leaks.
What You'll Learn
- • The full IDisposable pattern with finalizer safety net
- • using statements and C# 8 using declarations
- • IAsyncDisposable for async resource cleanup
- • SafeHandle and wrapping unmanaged resources
🧠 Real-World Analogy
The GC is like automatic housekeeping that cleans up .NET objects. But some things — like hotel room keys, rental cars, or library books — need to be returned explicitly. IDisposable is your "return the keys" contract. The using statement ensures you never forget.
The Dispose Pattern
Whenever your class holds unmanaged resources (file handles, database connections, network sockets), implement IDisposable. The pattern includes a protected virtual Dispose(bool) method and a finalizer as a safety net. Always call GC.SuppressFinalize(this) in Dispose to skip the finalizer when cleanup was done properly.
IDisposable & using Statement
Implement the full dispose pattern and see automatic cleanup with using.
using System;
using System.IO;
// The Dispose Pattern — proper implementation
class DatabaseConnection : IDisposable
{
private bool _disposed = false;
private string _connectionString;
public DatabaseConnection(string connectionString)
{
_connectionString = connectionString;
Console.WriteLine($"Connection opened: {_connectionString}");
}
public void ExecuteQuery(string sql)
{
ObjectDisposedException.ThrowIf(_disposed, this);
...IAsyncDisposable
When cleanup involves I/O operations (flushing streams, closing network connections), use IAsyncDisposable with await using. This avoids blocking threads during disposal.
IAsyncDisposable & await using
Implement async resource cleanup for I/O-bound disposal.
using System;
using System.IO;
using System.Threading.Tasks;
// IAsyncDisposable for async cleanup
class AsyncResource : IAsyncDisposable, IDisposable
{
private Stream? _stream;
private bool _disposed = false;
public AsyncResource(string path)
{
_stream = new MemoryStream();
Console.WriteLine($"Async resource created for: {path}");
}
public async Task WriteAsync(string data)
{
ObjectDisposedException.ThrowIf(_disposed, this);
...SafeHandle & Stacking using Declarations
For wrapping native OS handles, prefer SafeHandle subclasses over raw IntPtr — they provide guaranteed cleanup even if exceptions occur. C# 8+ using declarations let you stack multiple disposables cleanly.
SafeHandle & Stacked using
Wrap unmanaged resources safely and dispose multiple resources in order.
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
// SafeHandle for wrapping unmanaged resources
class UnmanagedBuffer : IDisposable
{
private SafeBuffer? _buffer;
private bool _disposed = false;
private readonly int _size;
public UnmanagedBuffer(int sizeInBytes)
{
_size = sizeInBytes;
// In real code, this would wrap a native handle
Console.WriteLine($"Allocated {sizeInBytes} bytes of unmanaged memory");
...Pro Tip
In .NET 7+, use ObjectDisposedException.ThrowIf(disposed, this) instead of manually throwing. It's cleaner and generates better error messages.
Common Mistakes
- • Not implementing IDisposable when holding file/network handles — causes resource leaks
- • Forgetting
GC.SuppressFinalize(this)— finalizer runs unnecessarily - • Accessing disposed objects — always check and throw ObjectDisposedException
Lesson Complete!
You now know how to properly manage resources in C#. Next, apply design patterns like Factory, Strategy, and Observer to build maintainable architectures.
Sign up for free to track which lessons you've completed and get learning reminders.