Enhance your code with Yield keyword


What we'll cover

  1. Custom iteration
  2. Stateful iteration

Requirements

  • Basic knowledge of C#
  • Visual Studio or IDE of choice

Custom Iteration

Have you faced a situation where you have to iterate a collection and extract part of it? A common approach is to create a temporary collection type, e.g. List<T>, add items to the list inside a loop, and finally return the new list. Let's see a simple example.

using System;
using System.Collections.Generic;
using DAL;

namespace ABClothing
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("TOP PURCHASE OF THE WEEK");
            Console.WriteLine("***************************");
            Console.WriteLine(" ");

            foreach (var customer in GetTopPurchase())
            {
                Console.WriteLine($"ID: {customer.CustomerId} - " +
                $"{customer.Name}: {string.Format("{0:C}", customer.TotalPurchase)}");
            }
        }

        static IEnumerable<Customer> GetTopPurchase()
        {
            var db = new Database();
            var customers = db.GetCustomers();
            var tempList = new List<Customer>();

            foreach (var customer in customers)
            {
                // Top purchase is $2000 and above
                if (customer.TotalPurchase >= 2000m)
                {
                    tempList.Add(customer);
                }
            }

            return tempList;
        }
    }
}

// Output

TOP PURCHASE OF THE WEEK
***************************

ID: 2 - Kulas Light: $2,300.10
ID: 3 - Ervin Howell: $2,750.20
ID: 5 - Patricia Lebsack: $2,200.35

In the example above, the code is printing customers with purchase up to $2,000 or above. Database method GetCustomers() returns a list of customers. The if condition filters out customers with purchase less than $2000. Customers with purchase amount up to $2000 or above are added to the temporary list tempList, and the list is returned when the loop ends.

Simple, isn't it? But we can do better. Let's change the code to use yield.

Stateful Iteration with yield

Below is GetTopPurchase() method modified with yield return customer.

...
static IEnumerable<Customer> GetTopPurchase()
{
    var db = new Database();
    var customers = db.GetCustomers();
    decimal _total = 0m; // new line added

    foreach (var customer in customers)
    {
        // Top purchase is $2000 and above
        if (customer.TotalPurchase >= 2000m)
        {
            _total += customer.TotalPurchase; // new line added
            yield return customer; // new line added
        }
    }
}

We no longer need tempList. Let's take a closer look and examine what's happening here.

According to documentation, the yield keyword is used in iterators for example a foreach. It removes the need to store state of the enumeration in a temporary variable. The call to GetTopPurchase() in the Main method returns an IEnumerable<Customer> into the foreach. Because IEnumerable<T> is returned, execution is deferred until the iterator is called. yield returns each element one at a time. You can end the iteration by using yield break statement.

In the first example above, tempList is used to store state. With yield that is not required because yield keeps track of the current iteration and the state of variables within the block. For example, yield remembers the state of _total variable for every iteration. This is possible because "when a yield return statement is reached in the iterator method, expression is returned, and the current location [block or scope] in code is retained. Execution is restarted from that location the next time that the iterator function is called". In our example, execution resumes at if statement the next time the iterator is called because it is the beginning of the scope where yield return is used.

Exception Handling:
A yield return statement can't be located in a try-catch block.
A yield return statement can be located in the try block of a try-finally statement.
A yield break statement can be located in a try block or a catch block but not a finally block.
If the foreach body (outside of the iterator method) throws an exception, a finally block in the iterator method is executed.

Why use yield?

  • Efficiency: You can iterate large sets of data with efficient use of memory. In the first example with tempList, 5 records are returned and 3 displayed. In the second example (with yield), one record is loaded into memory per iteration (when the if condition evaluates to true). No unused records lying around in memory.

Further reading

  1. Visit documentation page for yield contextual keyword to learn more.
  2. There is an interesting stackoverflow thread on the subject.
  3. Ash did a good job of explaining the benefits of using the yield keyword on this stackoverflow question: What are real life applications of yield?. I think you'll find it worth reading.

This is where I wrap up on this subject. I hope I have achieved my goal - to simplify the concept of yield. I am always looking for ways to improve, so I'll appreciate your comments. Do not forget to share and subscribe to my newsletter to stay up to date with my posts.

Comments