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 threwThe 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 declarationImplementing 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).
Disposemust 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. usingstatements and declarations callDisposeeven when exceptions occur, in reverse order.- Implement
IDisposablewhen your class owns disposable things; makeDisposeidempotent. - Use
await usingforIAsyncDisposabletypes.
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.
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 pass1.Why isn't the garbage collector enough to close a file?
2.What does a using statement guarantee?
3.With `using var a = ...; using var b = ...;` in a method, which is disposed first?
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)