Understand iterator methods and the yield keyword in C# with the help of examples

An iterator in C# is a method that utilizes the yield keyword to return elements one at a time to the caller. Such methods are very useful for traversing collections, without needing to create an intermediate collection inside the method for storing results.

This article will help you understand how to use yield by using practical examples.

The yield keyword can be used inside a method in two forms:

The yield return example

We assume an array with integers. We iterate over the elements and for each element add the current element to the sum previous elements to get the running sum.

We do not want to create an intermediate collection in our method for storing the running sums. Instead we use yield return to get the sum for each element and return it to the caller. The GetRunningSum method is now an iterator method, works as a state machine, since it remembers on which element it stopped, and proceeds to the next element when the caller allows it.

var integers = new int[] {1, 5, 12, 1, 2};
YieldTest yieldTest = new();

foreach (int i in yieldTest.GetRunningSum(integers))
{
    Console.WriteLine(i);
}

public class YieldTest
{
    public IEnumerable<int> GetRunningSum(int[] numbers)
    {
        var sum = 0;
        foreach (int number in numbers)
        {
            sum += number;
            yield return sum;
        }
    }
}

If you run this code in a console application, you will get:

1
6
18
19
21

The iterator-method waiting for an external action example

We now focus on a more complex example, where the same iterator method GetRunningSum, retains state and waits for the caller to resume the execution with the next element in the collection.

This time the caller is going to run extra code with each returned sum before letting the iterator method to proceed to the next element of the collection.

var integers = new int[] {1, 5, 12, 1, 2};
YieldTest yieldTest = new();

IEnumerator<int> enumerator = yieldTest.GetRunningSum(integers).GetEnumerator();

while (enumerator.MoveNext())
{
    Console.WriteLine("Current sum: " + enumerator.Current);
    Console.WriteLine("Press Enter to continue...");
    Console.ReadLine(); // Wait for user input
}

These are the main takeaways from this code:

The yield break example

The yield break; statement is used to end the iteration early based on a condition. In the following example we are updating the GetRunningSum with a condition for a sum exceeding a certain threshold:

    public IEnumerable<int> GetRunningSum(int[] numbers)
    {
        var sum = 0;
        foreach (int i in numbers)
        {
            sum += i;

            if (sum > 20)
            {
                yield break;
            }
            yield return sum;
        }
    }

The async yield example

It is possible to call asynchronous methods in the yield return <expression>;. For that you will have to use the IAsyncEnumerable<T> as a return type of the iterator method. This feature is supported starting from C# 8. Here is an example of an async call:

    public async IAsyncEnumerable<int> GetRunningSum(int[] numbers)
    {
        var sum = 0;
        foreach (int i in numbers)
        {
            sum += i;

            if (sum > 20)
            {
                yield break;
            }
            yield return await AnotherMethodAsync(sum);
        }
    }

Conclusion

The yield return simplifies the creation of iterators, allowing methods to return elements one at a time while maintaining their state between calls. This feature is particularly useful for processing large datasets or streams of data efficiently.

I hope my examples clarified how to use the keyword in your code.

comments powered by Disqus