Loading...

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


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