using and IDisposable

The garbage collector frees memory automatically, but some objects hold resources it knows nothing about: open files, network connections, database handles, locks. These must be released promptly and reliably. Such objects implement IDisposable, and the using statement guarantees their Dispose method runs — even when an exception is thrown.

The problem

A file stays open, and may stay locked, until it's closed. If an exception happens between opening and closing, the close line never runs:

C#
var writer = new StreamWriter("log.txt");
writer.WriteLine("Starting");
DoRiskyWork();          // if this throws...
writer.Dispose();       // ...this never runs and the file stays open

You could wrap it in try/finally, but C# has a shorter way.

The using statement

using (…) { … } creates the object, runs the block, and calls Dispose at the end — in a hidden finally, so it always happens:

C#
using (var writer = new StreamWriter("greeting.txt"))
{
    writer.WriteLine("Hello");
    writer.WriteLine("from a using block");
}   // writer.Dispose() runs here: the file is flushed and closed

Console.WriteLine(File.ReadAllText("greeting.txt"));
Output
Hello
from a using block

using declarations

The shorter using declaration drops the braces: the object is disposed when the variable goes out of scope — at the end of the enclosing block or method. This is the style you'll see most in modern code:

C#
WriteReport("report.txt", ["alpha", "beta", "gamma"]);
Console.WriteLine(File.ReadAllText("report.txt"));

static void WriteReport(string path, string[] items)
{
    using var writer = new StreamWriter(path);
    writer.WriteLine($"Report with {items.Length} items");
    foreach (string item in items)
        writer.WriteLine($"- {item}");
}   // disposed here
Output
Report with 3 items
- alpha
- beta
- gamma

Disposal order and exceptions

Several resources are disposed in reverse order of creation. And disposal still happens when an exception escapes:

C#
try
{
    using var a = new Resource("A");
    using var b = new Resource("B");
    Console.WriteLine("Working...");
    throw new InvalidOperationException("Something failed");
}
catch (InvalidOperationException ex)
{
    Console.WriteLine($"Caught: {ex.Message}");
}

class Resource : IDisposable
{
    private readonly string _name;
    public Resource(string name)
    {
        _name = name;
        Console.WriteLine($"Open {_name}");
    }
    public void Dispose() => Console.WriteLine($"Dispose {_name}");
}
Output
Open A
Open B
Working...
Dispose B
Dispose A
Caught: Something failed

Both resources were released before the catch block ran.

Implementing IDisposable

If your class owns a disposable object, it should be disposable too, and its Dispose should dispose what it owns. Make Dispose safe to call more than once, and have other methods refuse to work after disposal:

C#
using (var log = new FileLogger("app.log"))
{
    log.Write("App started");
    log.Write("User signed in");
}

Console.Write(File.ReadAllText("app.log"));

var closed = new FileLogger("other.log");
closed.Dispose();
closed.Dispose();          // safe: does nothing the second time
closed.Write("too late");

sealed class FileLogger(string path) : IDisposable
{
    private readonly StreamWriter _writer = new(path);
    private bool _disposed;
    private int _lines;

    public void Write(string message)
    {
        ObjectDisposedException.ThrowIf(_disposed, this);
        _writer.WriteLine($"{++_lines:D3} {message}");
    }

    public void Dispose()
    {
        if (_disposed) return;
        _writer.Dispose();
        _disposed = true;
    }
}
Output
001 App started
002 User signed in
Unhandled exception. System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'FileLogger'.

Classes that directly wrap unmanaged resources (raw operating-system handles) need a more elaborate pattern with a finaliser. That's rare in application code — wrap such handles in SafeHandle or use existing .NET classes, which already do it.

Common disposable types

  • FileStream, StreamReader, StreamWriter — files (lesson 54)
  • HttpResponseMessage — HTTP responses (lesson 57)
  • Database connections and commands, such as SqlConnection
  • CancellationTokenSource, Timer, SemaphoreSlim
  • Many objects from graphics, cryptography and compression libraries

A rule of thumb: if a type implements IDisposable and you created it, put it in a using. (A notable exception is HttpClient, which is designed to be created once and reused for the life of the application.)

using with any disposable

using works with any IDisposable, not just resources, which makes it handy for "do something now, undo it later" helpers. Here Indent() increases the indentation of log messages and the returned object restores it when disposed — even if an exception is thrown in between:

C#
Log.Write("Deploying");
using (Log.Indent())
{
    Log.Write("Building");
    using (Log.Indent())
    {
        Log.Write("Compiling 12 files");
    }
    Log.Write("Uploading");
}
Log.Write("Done");

static class Log
{
    private static int s_level;

    public static void Write(string message) => Console.WriteLine(new string(' ', s_level * 2) + message);

    public static IDisposable Indent()
    {
        s_level++;
        return new Restore();
    }

    private sealed class Restore : IDisposable
    {
        public void Dispose() => s_level--;
    }
}
Output
Deploying
  Building
    Compiling 12 files
  Uploading
Done

await using

Some resources need to do asynchronous work when they're released — flushing a network stream, say. They implement IAsyncDisposable, and you release them with await using (lesson 57 explains await):

C#
await using (var stream = new FileStream("data.bin", FileMode.Create))
{
    await stream.WriteAsync(new byte[] { 1, 2, 3, 4 });
}
Console.WriteLine(new FileInfo("data.bin").Length);
Output
4

Try it yourself

Write a Timer helper that prints how long a block took when it's disposed. (Timings vary from run to run, so this one shows no fixed output.)

Try it yourself · Program.cs
Edit the code, copy it into Program.cs and run it with dotnet run Program.cs (.NET 10 or later). How to set up .NET