.NET Academy

IDisposable and using

Release files, connections and other resources deterministically with IDisposable and using.

11 min read

You will learn

  • Explain why the GC isn't enough for resources like files and connections
  • Use using statements and declarations
  • Implement IDisposable correctly in your own class
  • Know about IAsyncDisposable and await using

Memory isn't the only resource

The GC frees memory — eventually. But programs also hold resources that are scarce and that other code is waiting for:

  • open files (others can't write to them while you hold them),
  • network sockets and HTTP connections,
  • database connections (a pool might allow only 100),
  • locks, timers, native memory, GPU buffers…

You can't wait for a garbage collection that might happen in a minute — you need to release these right when you're done. .NET's convention for that is the IDisposable interface:

public interface IDisposable
{
    void Dispose();
}

Any type holding such a resource implements it: FileStream, StreamReader, HttpClient, SqlConnection, DbContext, Timer…

using: dispose automatically

Calling Dispose() manually is error-prone — an exception could skip it. The using statement guarantees it:

using (var reader = new StreamReader("data.txt"))
{
    Console.WriteLine(reader.ReadLine());
}   // reader.Dispose() runs here, even if ReadLine threw

The compiler turns it into:

var reader = new StreamReader("data.txt");
try
{
    Console.WriteLine(reader.ReadLine());
}
finally
{
    reader?.Dispose();
}

The shorter using declaration disposes at the end of the enclosing scope (usually the method):

void Import()
{
    using var file = File.OpenRead("orders.csv");
    using var reader = new StreamReader(file);
    // ... work with reader ...
}   // reader disposed first, then file — reverse order of declaration

Implementing IDisposable

If your class owns something disposable (or a native resource), it should be disposable too:

public sealed class ReportWriter : IDisposable
{
    private readonly StreamWriter writer;
    private bool disposed;
 
    public ReportWriter(string path) => writer = new StreamWriter(path);
 
    public void WriteLine(string text)
    {
        ObjectDisposedException.ThrowIf(disposed, this);   // using a disposed object is a bug
        writer.WriteLine(text);
    }
 
    public void Dispose()
    {
        if (disposed) return;        // Dispose must be safe to call more than once
        disposed = true;
        writer.Dispose();            // dispose what you own
    }
}

Rules of thumb:

  • Dispose what you own, not what was given to you (if a stream is passed into your constructor, the caller usually owns it).
  • Dispose must be idempotent — calling it twice is harmless.
  • After disposal, other methods should throw ObjectDisposedException.
  • The full "dispose pattern" with Dispose(bool) and a finalizer is only needed for classes that directly hold unmanaged handles — rare in application code.

Async disposal

Some resources flush data or close connections asynchronously. They implement IAsyncDisposable, used with await using:

await using var db = new ShopDbContext();
var orders = await db.Orders.ToListAsync();

Who disposes in ASP.NET Core?

In web apps, objects created by the dependency injection container (like a scoped DbContext) are disposed by the container at the end of each request. You only dispose things you create with new. You'll see this in Track 8.

Key takeaways

  • Scarce resources (files, connections, sockets) must be released deterministically — that's IDisposable.
  • using statements and declarations call Dispose even when exceptions occur, in reverse order.
  • Implement IDisposable when your class owns disposable things; make Dispose idempotent.
  • Use await using for IAsyncDisposable types.

Exercise

Complete Connection.Dispose so it logs "close <name>" — but only the first time it's called. After disposal, Send must throw ObjectDisposedException. Then fix UseTwo so both connections are disposed automatically with using.

Program.cs
Loading editor…

C# runtime not loaded

Hint 1

Keep a private bool disposed field. Dispose returns early if it's already true.

Hint 2

throw new ObjectDisposedException(nameof(Connection)); — or use the helper ObjectDisposedException.ThrowIf(disposed, this);

Hint 3

In UseTwo, write using var a = ...; using var b = ...; — they're disposed at the end of the method, last one first.

Show solution
Console.WriteLine(string.Join(", ", UseTwo()));

List<string> UseTwo()
{
    var log = new List<string>();
    using (var a = new Connection("A", log))
    using (var b = new Connection("B", log))
    {
        a.Send("hello");
        b.Send("world");
    }
    return log;
}

class Connection : IDisposable
{
    private readonly string name;
    private readonly List<string> log;
    private bool disposed;

    public Connection(string name, List<string> log)
    {
        this.name = name;
        this.log = log;
        log.Add($"open {name}");
    }

    public void Send(string message)
    {
        ObjectDisposedException.ThrowIf(disposed, this);
    }

    public void Dispose()
    {
        if (disposed) return;
        disposed = true;
        log.Add($"close {name}");
    }
}

Quiz

4 questions · 3 correct to pass
  1. 1.Why isn't the garbage collector enough to close a file?

  2. 2.What does a using statement guarantee?

  3. 3.With `using var a = ...; using var b = ...;` in a method, which is disposed first?

  4. 4.What's the right way to dispose an object that implements IAsyncDisposable (e.g. a DbContext in async code)?

To complete this lesson

  • ○ Solve the exercise
  • ○ Pass the quiz (3/4 correct)