# Decompiling yield return: how iterator blocks become state machines


Some C# constructs compile to a single IL instruction. `yield return` compiles to an entire class. There is no "yield" opcode in the CLR, so when you write an iterator method, the compiler rewrites the whole thing into a generated type that implements `IEnumerator<T>` and remembers where it was between calls. Understanding that rewrite explains why a decompiler sometimes shows you a cryptic `<GetItems>d__0` with a `switch` in it — and how a good one turns that back into the `yield return` you actually wrote.

## The method you write vs. the method that ships

Here is an ordinary iterator:

```csharp
public IEnumerable<int> Evens(int max)
{
    for (int i = 0; i <= max; i++)
        if (i % 2 == 0)
            yield return i;
}
```

What ships in the assembly is almost none of that. The method `Evens` survives only as a stub that news up a generated class and returns it. The real work moves into a compiler-generated nested type — conventionally named `<Evens>d__0` — that implements `IEnumerable<int>`, `IEnumerator<int>`, and `IDisposable`. The decompiled skeleton looks like this:

```csharp
[CompilerGenerated]
private sealed class <Evens>d__0 : IEnumerable<int>, IEnumerator<int>, IDisposable
{
    private int <>1__state;    // where we are in the method
    private int <>2__current;  // the value the last yield produced
    public int max;            // the parameter, hoisted to a field
    private int <i>5__1;       // the local 'i', hoisted to a field

    int IEnumerator<int>.Current => <>2__current;
    bool IEnumerator.MoveNext() { /* the rewritten body — see below */ }
    // ... Reset/Dispose/GetEnumerator ...
}
```

Two kinds of fields appear. `<>1__state` and `<>2__current` are the machinery: the state field records where to resume, and the current field holds the value `Current` returns. The others — `max` and `<i>5__1` — are your parameter and your local `i`, **hoisted** from the stack onto the heap. They have to be fields, because a local on the stack would vanish the moment `MoveNext` returned, and the iterator needs `i` to still be there on the *next* call.

## MoveNext: a switch that resumes

The body of your iterator is rewritten into `MoveNext`, structured as a dispatch on `<>1__state`. Each `yield return` becomes three steps — stash the value, record a resume state, return `true` — and the matching `case` is where the next call jumps back in:

```csharp
bool IEnumerator.MoveNext()
{
    switch (<>1__state)
    {
        case 0:                         // first call: start the method
            <>1__state = -1;
            <i>5__1 = 0;                // i = 0
            break;
        case 1:                         // resume point after the yield
            <>1__state = -1;
            <i>5__1++;                  // the i++ from the for-loop
            break;
        default:
            return false;
    }

    while (<i>5__1 <= max)
    {
        if (<i>5__1 % 2 == 0)
        {
            <>2__current = <i>5__1;     // yield return i  →  stash value
            <>1__state = 1;             // remember where to resume
            return true;                //   and hand control back
        }
        <i>5__1++;
    }
    return false;                        // falling off the end = yield break
}
```

Read it as a resumable program. State `0` is the entry; the method runs until the first `yield return`, which sets `<>2__current`, sets the state to `1`, and returns `true`. The `foreach` consuming this iterator calls `MoveNext` again, the `switch` sees state `1` and jumps back *after* the yield to continue the loop. When the loop finally ends, `MoveNext` returns `false`, which is exactly what `yield break` (and falling off the end) compiles to — the signal to `foreach` that enumeration is over.

<figure class="my-8"><svg viewBox="0 0 640 150" role="img" aria-label="MoveNext dispatches on the state field: state 0 enters, runs to a yield return which sets current, sets state 1 and returns true; the next call resumes at state 1 after the yield; end returns false" style="width:100%;height:auto"><defs><style>.b{fill:#eef4ff;stroke:#8eb4ff}.y{fill:#e6fffb;stroke:#2dd4bf}.e{fill:#fff1f2;stroke:#fda4af}.t{font:600 12px system-ui;fill:#1547e0}.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="b" x="6" y="52" width="104" height="40" rx="6"/><text class="t" x="20" y="70">state 0</text><text class="s" x="20" y="85">enter</text><path class="a" d="M112 72 h40" marker-end="url(#h)"/><rect class="y" x="154" y="46" width="150" height="52" rx="6"/><text class="t" x="166" y="66">yield return i</text><text class="s" x="166" y="82">current=i; state=1</text><text class="s" x="166" y="94">return true →</text><path class="a" d="M306 72 h40" marker-end="url(#h)"/><rect class="b" x="348" y="52" width="120" height="40" rx="6"/><text class="t" x="360" y="70">state 1</text><text class="s" x="360" y="85">resume after yield</text><path class="a" d="M408 94 v24 h-180 v-20" marker-end="url(#h)"/><text class="s" x="250" y="132">loop: next MoveNext()</text><path class="a" d="M468 72 h40" marker-end="url(#h)"/><rect class="e" x="510" y="52" width="122" height="40" rx="6"/><text class="t" x="522" y="70" style="fill:#be123c">return false</text><text class="s" x="522" y="85">yield break / end</text></svg></figure>

## How the decompiler puts yield back

A decompiler reading the shipped assembly sees the generated class, not your method. Recovering the iterator is pattern recognition on a well-defined shape:

1. **Identify the state machine.** A nested `[CompilerGenerated]` type implementing `IEnumerator<T>` with a `<>1__state` and a `<>2__current` field is the signature of an iterator block — distinct from an async state machine, which implements `IAsyncStateMachine` and is driven by awaiters rather than `MoveNext`.
2. **Un-hoist the locals.** Fields like `<i>5__1` are mapped back to a local `i`, and fields matching the original parameters (`max`) are recognized as parameters of the reconstructed method. The `<name>5__n` encoding makes the original identifier recoverable.
3. **Fold the switch into control flow.** Each `case` in the `MoveNext` dispatch is a resume point that corresponds to a `yield return` site. The decompiler stitches the pre-yield code and post-resume code back into one linear body, turning `<>2__current = x; <>1__state = n; return true;` back into `yield return x;` and `return false;` at the end into the implicit `yield break`.

The result is your `Evens` method again — a `for` loop with a `yield return i` inside — rather than a 60-line state machine. [Glass.NET](https://delta1labs.com/products/glass) does this reconstruction by default and lets you drop to the raw `<Evens>d__0` view when you want to see the actual generated machinery, which is exactly where you look when an iterator misbehaves.

## Why it is worth knowing

The reconstructed `yield return` is what you want almost always, but the state machine underneath explains behaviour the high-level view hides. **Deferred execution** becomes obvious: the stub method only constructs the class, so no code in the iterator body runs until the first `MoveNext` — which is why an exception thrown "in" an iterator doesn't surface until you start enumerating. **The per-enumerator state** is visible as instance fields, explaining why two `foreach` loops over the same iterator each get their own independent walk. And when you are reading someone else's compiled library — or your own without symbols — recognizing `<...>d__` with a state/current pair tells you instantly that you are looking at an iterator, and the `case` labels in `MoveNext` map one-to-one to the `yield` points in the original. A decompiler that knows the pattern gives you the readable source; knowing the pattern yourself lets you read the machine when it matters.

