Card image cap

C# Fundamentals - Using the yield keyword

C# Fundamentals  • Posted 5 months ago

"yield" keyword is a contextual keyword in C# which can be used to convert any method, operator or get accessor which returns a collection of values into an "iterator" that "yields" some value on every iteration. It was first introduced in C# 2.0 which eliminated the need for creating own IEnumerable or IEnumerators for iterations.

Consider an example method which takes in a parameter and generates a list of some values which are further read at the caller end. Let's say we have the method definition and calling as below:


namespace IteratorApp
{
    public class YieldIterator 
    {
        public IEnumerable<string> GetListOfAppendedStrings(bool isAppended) 
        {
            var strings = new List<string>();

            if(!isAppended) 
            {
                return strings;
            }

            for(int i = 0; i < 10; i++) 
            {
                strings.Add(GetAppendedString(i, "This is some random string"));
            }

            return strings;
        }

        public string GetAppendedString(int index, string input) 
        {
            return $"{(index+100)}#{input}#{index*5}";
        }

        public void CallerMethod() 
        {
            foreach (var item in GetListOfAppendedStrings(true))
            {
                Console.WriteLine(item);
            }
        }
    }
}

In this approach, when the CallerMethod() calls the GetListOfAppendedStrings() method, the method body is executed completely and the resultant state is stored temporarily and is then passed onto the loop for displaying the list.

"yield" keyword eliminates the need for this temporary state variable, and instead tags the method GetListOfAppendedStrings() as an iterator of some values. When the caller invokes this method, the yield keyword returns a single execution of the method body one at a time. This execution continues untill the control finds a yield return or a return break statement.


namespace IteratorApp
{
    public class YieldIterator 
    {
        public IEnumerable<string> GetListOfAppendedStrings(bool isAppended) 
        {
            if(!isAppended) 
            {
                yield break;
            }

            for(int i = 0; i < 10; i++) 
            {
                yield return GetAppendedString(i, "This is some random string");
            }
        }

        public void CallerMethod() 
        {
            var items = GetListOfAppendedStrings(true);

            foreach (var item in items)
            {
                Console.WriteLine(item);
            }
        }
    }
}

In the above case, when the GetListOfAppendedStrings() method is first called, the method body is not yet executed and then when the foreach loop is executed, the method is now executed once per each time the iteration happens, and the execution of the method ends when the control finds either a "yield return " or a "yield break" statement. The "yield return " statement returns an expression, while "yield break" terminates the execution and returns an empty set. This eliminates the need for using an explicit variable that holds the state for us (in this case the strings variable) and instead the entire execution runs over references which can further help in memory consumptions.

the yield keyword can be used within a Method or a Get accessor which must have a return type of IEnumerable, IEnumerable, IEnumerator or IEnumerator which contain the MoveNext() method that triggers the execution of the method untill a "yield return" appears.

Since the entire execution occurs dynamically during runtime, we can't simply debug this execution flow with a normal breakpoint; breakpoints won't even hit. To solve this, we can simply invoke ToList() method onto the method return in order for the breakpoint to hit the method execution.


	public void CallerMethod() 
	{
          // makes the flow debuggable by breakpoints
          var items = GetListOfAppendedStrings(true).ToList();

          foreach (var item in items)
          {
              Console.WriteLine(item);
          }
	}

Another example of how yield can be used:


using System.Collections.Generic;

namespace IteratorApp
{
    public class YieldIterator 
    {
        // a get accessor which returns a set of strings
        public IEnumerable<string> ListOfIterativeStringsFromYield 
        {
            get 
            {
                yield return "This is string one";
                yield return "This is string two";
                yield return "This is string three";
                yield return "This is string four";
                yield return "This is string five";
                yield return "This is string six";
                yield return "This is string seven";
                yield return "This is string eight";
                yield return "This is string nine";
                yield return "This is string ten";
            }
        }

        public void CallerMethod() 
        {
            // iterate over the list returned by the get accessor
            foreach (var item in yieldIterator.ListOfIterativeStringsFromYield)
            {
                Console.WriteLine(item);
            }
        }
    }
}

And when you run this code, we get:


:\>dotnet run
This is string one
This is string two
This is string three
This is string four
This is string five
This is string six
This is string seven
This is string eight
This is string nine
This is string ten

As we can see, none of the string is repeated twice. Which means that in the above get accessor where we "yield return" one string per statement is invoked each time the loop iterates (by means of the MoveNext() method which is invoked for every iteration) and consecutive string values are "yield returned" to the loop.

yield and try-catch-finally:

A yield return statement can't occur within a try block that contains a catch block or within a finally block. Which means,


        public IEnumerable<string> ListOfIterativeStringsFromYield 
        {
            get 
            {
                try 
                {
                    yield return "This is string one";
                    yield return "This is string two";
                    yield return "This is string three";
                    yield return "This is string four";
                    yield return "This is string five";
                    throw new Exception("Hello Exception");
                    yield return "This is string six";
                    yield return "This is string seven";
                    yield return "This is string eight";
                    yield return "This is string nine";
                    yield return "This is string ten";
                    yield break;
                }
                catch(Exception ex) 
                {
                    Console.WriteLine(ex.Message);
                }
                finally 
                {
                    yield return "This is final string";
                    Console.WriteLine("Finally Here");
                }
            }
        }

Results in compilation errors:


YieldIterator.cs(49,21): error CS1626: Cannot yield a value in the body of a try block with a catch clause
YieldIterator.cs(68,21): error CS1625: Cannot yield in the body of a finally clause 

When we remove the catch block from our code and remove the "yield return" from the finally block, the compilation errors disappear and we get the following result:


:\> dotnet run
YieldIterator.cs(55,21): warning CS0162: Unreachable code detected
This is string one
This is string two
This is string three
This is string four
This is string five

Unhandled Exception: System.Exception: Hello Exception
   at ...
   at ...
Finally Here

In this way, we can make use of the yield contextual keyword to turn anything which returns a collection into an "iterator" that "yields" dynamically for every iteration.

We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept