# Decompiling using: the try/finally the compiler writes for you


Some C# statements map to a single IL instruction. `using` maps to a control-flow *shape* — a `try/finally` — that the compiler writes on your behalf so a resource is always cleaned up, even when the body throws. There is no `using` opcode; what ships is the try/finally, and a decompiler's job is to recognize that shape and fold it back into the one keyword you wrote. Walking through the lowering explains both what you see in a raw decompile and a couple of behaviours (`await using`, struct enumerators, the null check) that surprise people.

## The basic lowering

Take the canonical case:

```csharp
using (var stream = File.OpenRead(path))
{
    Process(stream);
}
```

The compiler lowers this to a `try/finally` whose finally disposes the resource. Decompiled without `using` recognition, it reads roughly:

```csharp
FileStream stream = File.OpenRead(path);
try
{
    Process(stream);
}
finally
{
    if (stream != null)
        ((IDisposable)stream).Dispose();
}
```

Three things to notice. The resource is acquired *before* the `try`, so a failure in the acquiring expression doesn't enter the protected region. The body goes in the `try`. And the `finally` disposes — unconditionally with respect to how the body exited (normal, `return`, or exception), which is the entire point of `using`: deterministic cleanup. The disposal is cast to `IDisposable` because that's the contract `using` targets, and it's guarded by a null check so a null resource is a harmless no-op rather than a `NullReferenceException` in the cleanup path.

A `using` *declaration* — the brace-less form added in C# 8 — lowers identically; the `finally` just runs at the end of the enclosing scope rather than a block you wrote:

```csharp
using var stream = File.OpenRead(path);
Process(stream);
// finally { if (stream != null) stream.Dispose(); } runs at end of scope
```

Same generated shape; the decompiler decides between the block and declaration forms by where the scope ends.

## The null check, and the struct exception to it

That `if (stream != null)` guard is a reliable tell, but it isn't always present — and its absence is informative. When the resource is a **value type that implements `IDisposable`**, the compiler does two things differently: it calls `Dispose()` directly on the struct (no null check — a struct is never null) and it avoids boxing it to `IDisposable`, calling the interface method on the value directly to keep the struct's semantics. This matters for the most common hidden `using` of all: `foreach`.

A `foreach` over a collection whose enumerator is a struct (like `List<T>.Enumerator`) lowers to a `using`-shaped try/finally around the enumeration, and because the enumerator is a struct, the finally calls `Dispose()` on it *without* a null check:

```csharp
// foreach (var item in list) { Use(item); }  lowers to:
List<int>.Enumerator e = list.GetEnumerator();
try
{
    while (e.MoveNext())
        Use(e.Current);
}
finally
{
    e.Dispose();        // struct enumerator — direct call, no null check, no boxing
}
```

So when a decompiler shows you a `try/finally` with a `MoveNext`/`Current` loop and an unguarded `Dispose` on a value, that's a `foreach`, and the missing null check tells you the enumerator was a struct. (If the enumerator were a class, you'd see the familiar `if (e != null) e.Dispose()` instead.)

<figure class="my-8"><svg viewBox="0 0 620 120" role="img" aria-label="A using statement lowers to: acquire the resource, a try around the body, and a finally that null-checks and disposes; a decompiler folds that shape back into using" style="width:100%;height:auto"><defs><style>.r{fill:#eef4ff;stroke:#8eb4ff}.y{fill:#fef9c3;stroke:#fde047}.g{fill:#e6fffb;stroke:#2dd4bf}.t{font:600 12px system-ui;fill:#1547e0}.m{font:11px ui-monospace,monospace;fill:#334155}.s{font:11px system-ui;fill:#475569}.a{stroke:#8eb4ff;stroke-width:2;fill:none}</style><marker id="h" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0 0l6 3-6 3z" fill="#8eb4ff"/></marker></defs><rect class="r" x="6" y="46" width="150" height="40" rx="6"/><text class="m" x="16" y="63">using (var x</text><text class="m" x="16" y="79">= expr) {...}</text><path class="a" d="M158 66 h34" marker-end="url(#h)"/><text class="s" x="150" y="38">compiles to</text><rect class="y" x="196" y="20" width="200" height="22" rx="4"/><text class="m" x="206" y="35">var x = expr;  // acquire</text><rect class="y" x="196" y="46" width="200" height="22" rx="4"/><text class="m" x="206" y="61">try { body }</text><rect class="y" x="196" y="72" width="200" height="30" rx="4"/><text class="m" x="206" y="87">finally { if (x!=null)</text><text class="m" x="206" y="99">  x.Dispose(); }</text><path class="a" d="M400 66 h34" marker-end="url(#h)"/><text class="s" x="402" y="38">folds back</text><rect class="g" x="438" y="46" width="176" height="40" rx="6"/><text class="m" x="448" y="63">using (var x</text><text class="m" x="448" y="79">= expr) {...}</text></svg></figure>

## await using and IAsyncDisposable

The async sibling, `await using`, lowers to the same try/finally — but the finally *awaits* `DisposeAsync()` instead of calling `Dispose()`:

```csharp
// await using (var conn = await OpenAsync()) { await Use(conn); }  lowers to (conceptually):
var conn = await OpenAsync();
try
{
    await Use(conn);
}
finally
{
    if (conn != null)
        await conn.DisposeAsync();   // a ValueTask, awaited in the finally
}
```

Because there's an `await` in the finally, the disposal becomes part of the method's [async state machine](https://delta1labs.com/blog/decompile-async-await-state-machine) — the `DisposeAsync()` call produces a `ValueTask` that the generated `MoveNext` parks on like any other await. In a raw decompile this looks like a state-machine resume point inside the fault/finally region, which is exactly how a decompiler identifies it: a `DisposeAsync` awaited in a finally is the signature of `await using`, and it's reconstructed as such. The same struct-vs-class null-check rule applies.

## How the decompiler folds it back

Recognizing `using` is pattern-matching on the generated shape, and a good decompiler is careful about it because not every `try/finally` is a `using`. The markers it looks for:

1. **A `finally` whose sole effect is a `Dispose()`/`DisposeAsync()` call** on a resource acquired immediately before the `try`. That coincidence of "acquire, try, finally-dispose" is the fingerprint.
2. **The null guard (or its struct-shaped absence)** around the Dispose, matching the compiler's own codegen — a hand-written `try/finally { x.Dispose(); }` that *doesn't* match the exact guarded shape is left as an explicit try/finally rather than mis-folded into `using`.
3. **For `foreach`**, the additional `GetEnumerator`/`MoveNext`/`Current` structure inside the `try`, which lets it reconstruct `foreach` rather than a bare `using` over an enumerator.

When the shape matches exactly, the decompiler collapses five or six lines of try/finally into the single `using` (or `foreach`, or `await using`) you wrote. When it doesn't — a `finally` that does more than dispose, or a disposal that isn't guarded the way the compiler guards it — it honestly shows the try/finally, because turning an arbitrary cleanup block into `using` would misrepresent the code.

## Why it's worth knowing

The folded `using` is what you want to read, but the try/finally underneath explains real behaviour. It's why `using` guarantees cleanup on an exception — the disposal is in a `finally`, not after the body. It's why disposing a `null` resource doesn't throw — the compiler guards it. It's why a `foreach` over a `List<T>` has *zero* disposal overhead you can see as boxing — the struct enumerator is disposed directly. And when you're reading someone else's assembly without symbols and you see a `try` with a lone guarded `Dispose` in the `finally`, you can read it straight back as a `using` even if the decompiler chose to show the raw shape. [Glass.NET](https://delta1labs.com/products/glass) folds these patterns back by default and lets you drop to the raw try/finally when you want to confirm exactly how the cleanup was generated — which is the view you want the moment a `Dispose` or `DisposeAsync` does something surprising.

