IDisposable and the Using Pattern in C#
Learn IDisposable and the using pattern in C# to manage resources safely, release unmanaged objects, and avoid leaks.
In C#, the IDisposable interface is designed to properly release resources used outside of memory
(such as files, network connections, database handles, GDI objects, etc.).
Instead of waiting for the system to clean these up automatically,
you perform deterministic cleanup using the Dispose method and the using construct.
What Is IDisposable?
The IDisposable interface contains only one method:
public interface IDisposable
{
void Dispose();
}
When a class implements IDisposable, it should release any managed or unmanaged resources it owns
inside the Dispose() method.
.NET’s garbage collector (GC) cannot automatically clean up unmanaged resources; therefore calling Dispose() is essential.
A Simple IDisposable Example
In the following example, the FileWriter class opens a file stream and implements IDisposable.
When Dispose() is called, the file is closed automatically.
using System;
using System.IO;
class FileWriter : IDisposable
{
private readonly StreamWriter _writer;
private bool _disposed = false;
public FileWriter(string path)
{
_writer = new StreamWriter(path);
}
public void Write(string text)
{
if (_disposed)
throw new ObjectDisposedException(nameof(FileWriter));
_writer.WriteLine(text);
}
public void Dispose()
{
if (!_disposed)
{
_writer.Close();
_writer.Dispose();
_disposed = true;
Console.WriteLine("File closed and resources released.");
}
}
}
class Program
{
static void Main()
{
var writer = new FileWriter("test.txt");
writer.Write("Hello, World!");
writer.Dispose(); // resources released
}
}
Resource Management with using
The using keyword automatically cleans up objects that implement IDisposable.
When the using block ends, the object’s Dispose() method is called automatically.
using (var file = new StreamWriter("log.txt"))
{
file.WriteLine("Application started: " + DateTime.Now);
} // Dispose() is called automatically here
This prevents resource leaks even if an error occurs. A using block is equivalent to try/finally:
var file = new StreamWriter("log.txt");
try
{
file.WriteLine("Application started.");
}
finally
{
file.Dispose();
}
Multiple using on One Line
Starting with C# 8.0, the “using declaration” offers a cleaner syntax.
Without braces, Dispose() is still called automatically when the scope ends.
using var file = new StreamWriter("output.txt");
file.WriteLine("This file will close automatically when the program ends.");
The Dispose Pattern (Advanced)
If your class owns unmanaged resources or will be subclassed by types that hold other IDisposable objects,
implement the Dispose Pattern. This pattern uses Dispose(bool disposing) to separately
release managed and unmanaged resources.
using System;
class ResourceManager : IDisposable
{
private IntPtr _unmanagedResource; // e.g., file handle, socket, GDI object
private bool _disposed = false;
public ResourceManager()
{
_unmanagedResource = IntPtr.Zero; // example initialization
}
// Public Dispose method
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // Prevent finalizer from running
}
// Protected virtual method; subclasses can override
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
// release managed resources here
}
// release unmanaged resources here
if (_unmanagedResource != IntPtr.Zero)
{
// e.g., CloseHandle(_unmanagedResource);
_unmanagedResource = IntPtr.Zero;
}
_disposed = true;
}
// Finalizer (safety net for unmanaged resources)
~ResourceManager()
{
Dispose(false);
}
}
This structure prevents unnecessary finalizer calls via GC.SuppressFinalize() and
ensures unmanaged resources are safely released.
Asynchronous Resource Management: IAsyncDisposable
Introduced in C# 8.0, the IAsyncDisposable interface is used for asynchronous cleanup of resources.
For example, a network connection or stream can be closed asynchronously.
using System;
using System.IO;
using System.Threading.Tasks;
class LogWriter : IAsyncDisposable
{
private readonly StreamWriter _writer = new StreamWriter("async_log.txt");
public async ValueTask DisposeAsync()
{
await _writer.FlushAsync();
_writer.Dispose();
Console.WriteLine("Asynchronous log writer closed.");
}
public async Task WriteAsync(string message)
{
await _writer.WriteLineAsync(message);
}
}
class Program
{
static async Task Main()
{
await using var log = new LogWriter();
await log.WriteAsync("Program started.");
}
}
Common Pitfalls and Things to Watch
- Dispose() should be safe to call multiple times (idempotent).
- Call
GC.SuppressFinalize()only after managed dispose has completed. - Inside a
usingblock you won’t have leaks; outside of it you must callDispose()manually. - If unmanaged resources exist, don’t forget to add a finalizer.
- Use
IAsyncDisposablefor asynchronous resources.
Example: A File Handling Service
In the following example, a service class manages resources like FileStream and StreamReader.
Thanks to using, files are closed automatically when operations complete.
using System;
using System.IO;
class FileService
{
public string Read(string path)
{
using var sr = new StreamReader(path);
return sr.ReadToEnd();
}
public void Write(string path, string data)
{
using var sw = new StreamWriter(path, append: true);
sw.WriteLine(data);
}
}
class Program
{
static void Main()
{
var service = new FileService();
service.Write("report.txt", "New record added.");
Console.WriteLine(service.Read("report.txt"));
}
}
TL;DR
- IDisposable enables manual release of resources via
Dispose(). - The using construct calls
Dispose()automatically to prevent leaks. - The Dispose Pattern safely cleans both managed and unmanaged resources.
- IAsyncDisposable closes resources asynchronously (e.g., file, network).
- GC.SuppressFinalize() improves performance by preventing unnecessary finalizer calls.
Related Articles
C# Exception Handling (try, catch, finally)
Learn how to handle exceptions in C# using try, catch, and finally blocks to manage errors safely with clear examples.
Class, Object, Property and Methods in C#
Learn how classes, objects, properties, and methods work in C# and form the core building blocks of object-oriented programming.
File IO and Stream API in C#
Learn file input/output and Stream API in C#, including reading, writing, and managing file data efficiently.
Interop in C# (Working with C/C++ Libraries)
Learn how to use Interop in C# to work with C/C++ libraries, including P/Invoke, unmanaged code, and data marshaling.
Memory Management and the Garbage Collector in C#
Learn memory management and the garbage collector in C# to understand object lifetime, allocation, and cleanup processes.