How C# Using Declarations Work Without Curly Braces
Introduced in C# 8.0, the using declaration allows
developers to manage IDisposable objects cleanly without
wrapping code in nested curly braces. This article explains how
declaration-style using statements work under the hood, how
their scope and lifetime are determined, the order in which resources
are disposed of, and when you should still consider using the
traditional block syntax.
The
Traditional using Statement vs. using
Declaration
Historically, disposing of a resource required a using
statement with an explicit code block:
using (var stream = new FileStream("test.txt", FileMode.Open))
{
// Work with stream
} // stream.Dispose() is called hereWhile effective, working with multiple resources led to deeply nested code (often called the "pyramid of doom").
A using declaration removes the parentheses and the
curly braces by prepending the using keyword directly to a
local variable declaration:
using var stream = new FileStream("test.txt", FileMode.Open);
// Work with streamHow the Lifetime and Scope Work
Instead of defining lifetime through an explicit block marked by
{ ... }, a using declaration binds the
lifetime of the variable to the enclosing scope in which it is
declared.
The compiler automatically emits a try/finally block
behind the scenes. The resource's Dispose() method is
guaranteed to run when the program execution exits the containing
scope—whether that exit happens via a return statement, an exception, or
reaching the end of the block.
Consider the following method:
void ProcessData()
{
using var reader = new StreamReader("data.txt");
string line = reader.ReadLine();
Console.WriteLine(line);
} // reader.Dispose() is invoked hereThe variable reader lives until ProcessData
finishes. The closing brace } of the method acts as the
boundary where disposal occurs.
Disposal Order with Multiple Declarations
When you declare multiple resources within the same scope using this syntax, they are disposed of in reverse order of their declaration (Last-In, First-Out):
void CopyData()
{
using var source = File.OpenRead("source.bin");
using var destination = File.Create("destination.bin");
source.CopyTo(destination);
}
// 1. destination.Dispose() is called first
// 2. source.Dispose() is called secondThis order prevents dependency issues, ensuring that secondary resources depending on earlier ones are safely closed first.
Controlling Scope Without Methods
Because a using declaration lasts until the end of its
enclosing scope, it respects any set of curly braces, such as loops,
conditional statements, or arbitrary blocks:
void ExecuteTask()
{
// Arbitrary block to limit resource lifetime
{
using var client = new HttpClient();
// client is used here
} // client.Dispose() is called here
// Perform long-running work where the client is no longer needed
}When to Avoid
using Declarations
While using declarations reduce visual clutter, the
traditional block-scoped using statement is still useful in
scenarios where a resource locks a file or consumes significant memory
and must be released immediately—well before the surrounding method
completes. In such cases, the explicit boundary of a traditional
using block ensures the resource is not held open
unnecessarily.