While the above has no value for anything serious, it’s a good example to debug to see how the yield return statement works. Let’s call this method:

foreach(var number in GetNumbers()) Console.WriteLine(number);

When you debug this (using F11, Step Into), you will see how the current line of execution jumps between the foreach-loop and the yield return statements. What happens here is that each iteration of the foreach loop calls the iterator method until it reaches the yield return statement. Here the value is returned to the caller and the location in the iterator function is saved. Execution is restarted from that location the next time that the iterator function is called. This continues until there are no more yield returns.

A first use case of the yield statement is the fact that we don’t have to create an intermediate list to hold our variables, such as in the example above. There are a few more implications though.

Yield return versus traditional loops

Let’s have a look at a different example. We’ll start with a traditional loop which returns a list:

At first sight, we might think that this is a function which returns a list of 5 numbers. However, because of the yield-statement, this is actually something completely different. This method doesn’t in fact return a list at all. What it does is it creates a state-machine with a promise to return 5 numbers. That’s a whole different thing than a list of 5 numbers. While often the result is the same, there are certain subtleties you need to be aware of.

This is what happens when we execute this code:

GenerateWithYield is called.

This returns an IEnumerable. Remember that it’s not returning a list, but a promise to return a sequence of numbers when asked for it (more concretely, it exposes an iterator to allow us to act on that promise).

Each iteration of the foreach loop calls the iterator method. When the yield return statement is reached the value is returned, and the current location in code is retained. Execution is restarted from that location the next time that the iterator function is called.

The end result is that you get the numbers 1 to 5 printed in the console.

Example: infinite loops

Now you might think that since both example behave exactly the same, that there’s no difference in which one we use. Let’s modify the example a bit to show where the differences lie. I’m going to make two small changes:

Instead of looping in the generator until we reach 5, I’m going to loop endlessly:

Instead of iterating directly over the list, I’m going to take 5 items of the list:

foreach(var number in GenerateWithoutYield().Take(5))
Console.WriteLine(number);
foreach(var number in GenerateWithYield().Take(5))
Console.WriteLine(number);

When we do this, the difference is clear. Following the previously described steps, in the case of the method without yield, the loop will never finish as it will keep looping forever inside the GenerateWithoutYield-method when it’s called in the first step (until it throws an OutOfMemoryException). In the case of the GenerateWithYield-method however, we get a different behavior. Because the Take-method is actually implemented with a yield return operator as well, this will succeed. The method only gets called until the Take-method is satisfied.

Example: multiple iterations

Another side effect of the yield return statement is that multiple invocations will result in multiple iterations. Let’s have a look at an example:

Read through the above code sample and try to predict what will be written to the console.

What do you think the output is here? 20? In fact, the result is 10. Let’s see why:

When the line var invoices = GetInvoices(); is executed we’re not getting a list of invoices, we’re getting a state-machine that can create invoices.

That state machine is then passed to the DoubleAmounts-method.

Inside the DoubleAmounts-method we use the state-machine to generate the invoices and we double the amount of each of those invoices.

All the invoices that were created are discarded though, as there are no references to them.

When we return to the main method, we still have a reference to the state-machine. By calling the First-method we again ask it to generate invoices (only one in this case). The state-machine again creates an invoice. This is a new invoice and as a result, the amount will be 10.

Because this is non-obvious behavior, tools such as Resharper will warn you about multiple iterations.

Real life usage

It’s pretty neat that we can write seemingly infinite loops and get away with it, but what can we use it for in real life? In broad terms, I’ve found two main use cases (all other use cases I found are a subclass of these two).

Custom iteration

Let’s say we have a list of numbers. We now want to display all the numbers larger than a specific number. In a traditional implementation that might look like this:

The above code will output the values 1,3,6,10,15. Because of the pause/resume behavior, the variable total will hold its value between iterations. This can be handy to do stateful calculations.

Deferred execution

All of the above samples have one thing in common: they only get executed as and when necessary. It’s the mechanism of pause/resume in the methods that makes this possible. By using deferred execution we can make some methods simpler, some faster and some even possible where they were impossible before (remember the infinite number generator).

The entire LINQ part of C# is built around deferred execution. Let’s see a few sample how deferred execution can make things more efficient:

Suppose we have 1000 products. If the above method did not have deferred execution, it would mean we would:

Fetch all 1000 products

Calculate the price of all 1000 products

Order 1000 prices

Convert all the prices to dollars

Take the top 5 of those prices

Because of deferred execution however, this can be reduced to:

Fetch 10 products

Calculate the price of 10 products

Order 10 prices

Convert 5 of these prices to dollars

While maybe a contrived example, it shows clearly how deferred execution can greatly increase efficiency.

Side note: I want to make clear that deferred execution in itself does not make your code faster. Inherently, it has no effect on the speed or efficiency of your code. The value of deferred execution is that it allows you to optimize your code in a clean, readable and maintainable way. This is an important distinction.

Conclusion

The yield-keyword is often misunderstood. Its behavior can seem a bit strange at first sight. However, it’s often the key to creating efficient code that is maintainable at the same time. Its main use cases are custom and stateful iteration which allow you to create simple yet powerful code. The yield-keyword is what’s powering the deferred execution used in LINQ and allows us to use it in our code. I hope this article helped explaining the semantics of the yield-keyword and the effects and implications it has on calling code. Feel free to ask any questions in the comments!