Using Iterator Blocks (C#)

Cross-posted from Ryan's blog.

Iterator blocks (more broadly called generators) are a special syntax for producing an enumerator. They allow fine-grained control over iteration, including:

  • Controlling the order of iteration
  • Modifying a collection during iteration
  • Loading a large collection in batches
  • Creating the collection as it is consumed
  • Implementing infinite sequences

An iterator block is implemented by creating a method or property getter that returns IEnumerable and using yield return statements.

Here’s an iterator that returns even numbers in the range [firstNumber..lastNumber].

IEnumerable<int> EvenSequence(int firstNumber, int lastNumber)
{
    for (int number = firstNumber; number <= lastNumber; number++)
    {
        if (number % 2 == 0)
        {
            yield return number;
        }
    }
}

// usage:
foreach (int number in EvenSequence(5, 18))
{
    Console.Write(number.ToString() + " ");
}
// Output: 6 8 10 12 14 16 18

Behind the scenes the compiler replaces an iterator block with a nested class that implements IEnumerable and IEnumerator. When MoveNext() is called on this class, code is executed until it either reaches a yield return, yield break, or the end of the block. A yield return statement sets the Current property to the return value and returns true from MoveNext(). yield break or reaching the end of the code block causes MoveNext() to return false. On each call to MoveNext(), code execution resumes after the previous yield return statement. The internal state of the iterator block is maintained between calls. (See my previous post on foreach if this did not make sense.)

Note, the Reset() method is not supported when consuming an iterator block.

Infinite Sequences

You can leverage an iterator block to write an infinite sequence generator. Here is a Fibonacci sequence generator:

IEnumerable<long> Fibonacci()
{
    yield return 1;
    yield return 2;
    long n = 1, m = 2;
    while (true)
    {
        var next = n + m;
        yield return next;
        n = m;
        m = next;
    }
}

The first time MoveNext() is called this yields 1. The second time it yields 2. The third time all the code through yield return next; is executed. The enumerator's Current property is set to the value of next and MoveNext() returns true. Each subsequent call to MoveNext() resumes at n = m;. Since the loop never exits, you can call MoveNext() an unlimited number of times.

The code below consumes Fibonacci() to calculate the sum of all even Fibonacci numbers less than or equal to 4,000,000.

long sum = 0;
foreach (var n in Fibonacci())
{
    if (n%2 != 0) continue;
    if (n > 4000000) break;
    sum += n;
}

Multiple Yield Return Statements

You can use multiple yield return statements to emulate a collection initializer. I find this useful when I need to conditionally include elements in a collection.

IEnumerable<string> Foo() {
    yield return “First”;
    yield return “Second”;
    yield return “Third”;
}

Yield Break

yield break exits an iterator block early.

IEnumerable<string> Foo(bool exitEarly) {
    yield return “First”;
    yield return “Second”;
    if(exitEarly) yield break;
    yield return “Third”;
}

Try...Finally

You can’t yield return or yield break from inside of a finally block. This is because a finally block will not be called until the enumerator is being disposed by foreach.

IEnumerable<int> Foo() {
    try {
        yield return 1;
        yield return 2;
        yield return 3;
    } finally {
        Console.WriteLine("Finally block!");
    }
}