using and IDisposable
Release files, sockets and connections at a known moment with IDisposable, the using statement, using declarations and await using.
using StreamReader reader = File.OpenText(path);Some objects hold something that lives outside your program's memory: an open file, a network socket, a database connection. The operating system allows only so many of those at once, so each must be handed back when you are done. The method that does the handing back is Dispose. A type that has one implements the interface IDisposable, which declares exactly one member: void Dispose().
using is try/finally
Calling Dispose by hand is easy to get wrong, because an exception can jump over the call. The using statement writes the cleanup for you. These two are the same code:
using (StreamReader reader = File.OpenText("in.txt"))
{
Console.WriteLine(reader.ReadLine());
}
// What the compiler turns it into:
StreamReader reader = File.OpenText("in.txt");
try
{
Console.WriteLine(reader.ReadLine());
}
finally
{
reader?.Dispose();
}Shorter and asynchronous forms
A using declaration, added in C# 8, drops the braces: using StreamReader reader = File.OpenText(path);. The object is then disposed at the end of the enclosing scope, usually the end of the method. Types whose cleanup involves waiting, such as flushing bytes to a disk or closing a connection politely, implement IAsyncDisposable with a DisposeAsync method, and you write await using instead. The await keyword is covered in the asynchronous stage; the cleanup rule is identical.
What trips people up
Disposing is not deleting. The variable still refers to the object, and most types throw ObjectDisposedException if you use them afterwards. The variable a using declares is read-only, so you cannot reassign it.
Example
// A using statement disposes at the closing brace.
using (Connection first = new Connection("a"))
{
first.Send("hello");
}
// A using declaration disposes at the end of the enclosing scope, which here
// is the end of the program. Both forms compile to the same try/finally.
using Connection second = new Connection("b");
second.Send("world");
Console.WriteLine("work finished");
class Connection : IDisposable
{
private readonly string _name;
public Connection(string name)
{
_name = name;
Console.WriteLine($"open {_name}");
}
public void Send(string text) => Console.WriteLine($"{_name} sends {text}");
// Called by the using statement. Must be safe to call more than once.
public void Dispose() => Console.WriteLine($"close {_name}");
}
// Output:
// open a
// a sends hello
// close a
// open b
// b sends world
// work finished
// close bWhen to use it
- Closing a file handle even when the code reading the file throws halfway through.
- Returning a pooled database connection the moment a request is finished with it.
- Writing your own type that wraps an unmanaged handle and must release it.
More examples
Reading a file with a using declaration
ReadLine returns null at the end of the file, which is why line is declared as string? under nullable reference types. The File and StreamReader types live in System.IO, and dotnet new console enables implicit usings, so no using directive is needed for them. File.WriteAllText and File.ReadAllText open and close the file themselves, so they need no using at all.
string path = "numbers.txt";
File.WriteAllText(path, "10\n20\n");
// File.OpenText returns a StreamReader, which holds an open file handle.
using StreamReader reader = File.OpenText(path);
string? line;
while ((line = reader.ReadLine()) is not null)
{
Console.WriteLine($"read {line}");
}
// reader.Dispose() runs here, closing the handle.
// Output:
// read 10
// read 20Two resources, disposed in reverse order
Several instances of the same type can share one using statement, and they are disposed in reverse order of declaration. That order matters when one resource is layered on another, such as a writer wrapping a stream: the outer one must flush before the inner one closes.
// Declaring both in one using statement is shorthand for nesting two of them.
using (StreamReader input = File.OpenText("in.txt"),
extra = File.OpenText("extra.txt"))
{
Console.WriteLine(input.ReadLine());
Console.WriteLine(extra.ReadLine());
}
// The longhand, which disposes in the same order: extra first, then input.
using (StreamReader input2 = File.OpenText("in.txt"))
{
using (StreamReader extra2 = File.OpenText("extra.txt"))
{
Console.WriteLine(input2.ReadLine());
Console.WriteLine(extra2.ReadLine());
}
}await using for asynchronous cleanup
FileStream implements both IDisposable and IAsyncDisposable, so either form compiles; await using is the one to use in code that is already asynchronous, because the synchronous Dispose would block a thread during the flush. Keyword await and the whole asynchronous model are taught in the asynchronous stage - this is here so the shape is recognisable when you meet it.
// Top-level statements may use await; the compiler makes the entry point async.
await using FileStream stream = File.Create("out.bin");
await stream.WriteAsync(new byte[] { 1, 2, 3 });
// DisposeAsync runs at the end of the scope. It flushes the buffered bytes to
// disk and closes the handle without blocking the thread while it waits.
Discussion