Understanding the C# yield Keyword

1. What Does yield Do?

yield return

The code below demonstrates how yield return enables iteration over a sequence, similar to using foreach on an array. In essence, yield return is used to create an iterator.

using static System.Console;
using System.Collections.Generic;

class Program
{
    // A method returning IEnumerable<int>, containing three yield return statements
    public static IEnumerable<int> GetNumbers()
    {
        yield return 1;
        yield return 2;
        yield return 3;
    }

    static void Main(string[] args)
    {
        // Iterate over the sequence using foreach
        foreach (int item in GetNumbers())
        {
            WriteLine(item);
        }
        ReadKey();
    }
}

// Output:
// 1
// 2
// 3

yield break

In the following example, only 1 and 2 are printed; 3 is not. This is because yield break terminates the iterator early.

using static System.Console;
using System.Collections.Generic;

class Program
{
    public static IEnumerable<int> GetNumbers()
    {
        yield return 1;
        yield return 2;
        yield break;
        yield return 3; // This line is never reached
    }

    static void Main(string[] args)
    {
        foreach (int item in GetNumbers())
        {
            WriteLine(item);
        }
        ReadKey();
    }
}

// Output:
// 1
// 2

2. Where Can yield Be Used?

The yield keyword can only appear in methods, operators, or get accessors that have a return type of IEnumerable, IEnumerable<T>, IEnumerator, or IEnumerator<T>.

3. How Does yield Work Under the Hood?

Let's replace foreach with a while loop. Even though we did not manual implement GetEnumerator(), MoveNext(), or the Current property, the code still works.

class Program
{
    public static IEnumerable<int> GetNumbers()
    {
        yield return 1;
        yield return 2;
        yield return 3;
    }

    static void Main(string[] args)
    {
        // Using while loop instead of foreach
        IEnumerator<int> enumerator = GetNumbers().GetEnumerator();
        while (enumerator.MoveNext())
        {
            int current = enumerator.Current;
            WriteLine(current);
        }
        ReadKey();
    }
}

// Output:
// 1
// 2
// 3

To understand why this works, we can decompile the compiled assembly (e.g., using ILSpy).

When decompiling to C#, the code looks unchanged. How ever, if we decompile to IL with C# comments, we see that the compiler automatically generates a new class.

ILSpy decompilation showing automatic class generation

The GetNumbers() method returns an instance of this generated class.

GetNumbers returning generated class instance

This generated class implements IEnumerable, IEnumerable<T>, IEnumerator, and IEnumerator<T>. That is why we can use MoveNext() and Current without implementing them manually.

Generated class implementing enumerator interfaces

Let's look at how the MoveNext() method is implemented in this generated class.

MoveNext implementation showing state machine

State machine details

Each call to MoveNext() increments a state field. The method uses a switch statement (based on state) to execute different parts of the original method. For the example with three yield return statements, MoveNext() returns true three times (states 1, 2, 3) and then returns false (state 4), ending the iteration.

Actual Flow of Iteration Using yield

  1. The method (e.g., GetNumbers()) is called, returning an instance of the compiler-generated class.
  2. GetEnumerator() is called on that instance, returning the iterator (the same instance often implements both IEnumerable and IEnumerator).
  3. Each call to MoveNext() increments state and uses a switch to execute the correct code segment, until MoveNext() returns false.

In short, the yield keyword is syntactic sugar. The compiler trensforms the method into a state machine that implements the IEnumerable<T>, IEnumerable, IEnumerator<T>, and IEnumerator interfaces, thus providing the iteration functionality.

Tags: C# yield iterator syntactic sugar State Machine

Posted on Mon, 02 Nov 2026 16:32:42 +0000 by Sinemacula