Yield keyword in C# – an illustrated example

So I was asked the other day about the Yield keyword in C#, specifically, “What’s the idea” with it? It seems there are quite a few who have never used it, simply because the intent isn’t totally clear. The MSDN documentation is a little vague IMHO:

http://msdn.microsoft.com/en-us/library/vstudio/9k7k7cf0.aspx

I’ve put together a little project to illustrate this.

https://github.com/melchizidech/YieldExploration

The interesting stuff happens in the Collections file.

namespace YieldExploration
{
    using System;
    using System.Collections.Generic;

    /// 
    /// IEnumerable generator class
    /// 
    static class Collections
    {
        /// 
        /// Compute the strings, add them to a list, return a ref to that list
        /// 
        /// 
        /// The .
        /// 
        public static IEnumerable Computed1()
        {
            var result = new List();
            for (int i = 0; i < 3; i++)
            {
                result.Add(Compute(i));
            }

            return result;
        }

        /// 
        /// Compute the strings, add each one to to a list, yield return a ref to that element inside the loop.
        /// 
        /// 
        /// The .
        /// 
        public static IEnumerable Computed2()
        {
            var result = new List();
            for (int i = 0; i < 3; i++)
            {
                var item = Compute(i);
                result.Add(item);
                yield return item;
            }
        }

        /// 
        /// Compute the strings, add each one to to a list, then loop over the list and yield return each element.
        /// 
        /// 
        /// The .
        /// 
        public static IEnumerable Computed3()
        {
            var result = new List();
            for (int i = 0; i < 3; i++)
            {
                var item = Compute(i);
                result.Add(item);
            }

            for (int i = 0; i < 3; i++)
            {
                yield return result[i];
            }
        }

        /// 
        /// Loop over the list and yield return a pointer to the compute() function which generates the element.
        /// 
        /// 
        /// The .
        /// 
        public static IEnumerable Computed4()
        {
            for (int i = 0; i < 3; i++)
            {
                yield return Compute(i);
            }
        }

        private static string Compute(int i)
        {
            Console.WriteLine("Computing {0}", i);
            return string.Format("Computed: {0}", i);
        }
    }
}

Which is called thusly:

namespace YieldExploration
{
    using System;

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Computed 1");
            foreach (string item in Collections.Computed1())
            {
                Console.WriteLine(item);
            }

            Console.WriteLine();
            Console.WriteLine("Computed 2");

            foreach (string item in Collections.Computed2())
            {
                Console.WriteLine(item);
            }            

            Console.WriteLine();
            Console.WriteLine("Computed 3");

            foreach (string item in Collections.Computed3())
            {
                Console.WriteLine(item);
            }

            Console.WriteLine();
            Console.WriteLine("Computed 4");

            foreach (string item in Collections.Computed4())
            {
                Console.WriteLine(item);
            }

            Console.WriteLine();
            Console.ReadLine();
        }
    }
}

When I run this I get the following output:


Computed 1
Computing 0
Computing 1
Computing 2
Computed: 0
Computed: 1
Computed: 2

Computed 2
Computing 0
Computed: 0
Computing 1
Computed: 1
Computing 2
Computed: 2

Computed 3
Computing 0
Computing 1
Computing 2
Computed: 0
Computed: 1
Computed: 2

Computed 4
Computing 0
Computed: 0
Computing 1
Computed: 1
Computing 2
Computed: 2

Yield is basically fine-grained control over the deferred execution of an IEnumerable.
As you can see, the only case where the console.writeline is called along with the generation of the string which is printed by program main are methods 2 and 4. Methods 1 and 3 are also identical.

In each case, the IEnumerable is evaluated and the results printed. In the first and third methods, the evaluation of the IEnumerable causes the sequence of numbers to be printed, and then re-printed by the calling method.

In the second and fourth, the evaluation of each distinct element in the Enumerable is deferred, so that the code inside the compute() method is only executed when the item is retrieved from the Enumerable.

Hopefully this makes the use of yield a little clearer.

Advertisements

About this entry