Share via


Chuck is all about the yield statement

One of the cool things about working on Cider is that I get to work closely with Chuck. We were chatting the other day about the new yield statement, which I hadn't played with yet - but he walks through in his article on building a LinkedList.

Old-school IEnumerable Background
The foreach statement works on anything that implements IEnumerable (typically collections implement this). IEnumerable has one method called GetEnumerator. As the name suggests, an enumerator knows how to walk through your collection and enumerate what's in it. IEnumerator is super simple - it just has Current, MoveNext and Reset.

Because the foreach statement takes an enumerator, you can build custom enumerators to do interesting things with your collection without creating unnecessary copies of the entire list. 

The enumerator fundamentally controls two things: the order of how you walk through your collection and what kind of data is returned. As the Current property on the enumerator is of type object, you're not just limited to returning Person objects from the PersonCollection - if you just wanted to iterate over the ages - you could build an age enumerator that return Person.Age instead. 

(This trick is particularly handy when you think you might need to maintain two separate collections of things and keep them in sync for some reason - if you can stuff one as a property on the other and build a custom enumerator you only wind up with one list to maintain.)

The yield statement
So IEnumerable/IEnumerator is cool, powerful and whatnot - but you have to put together a lot of glue to make it all work. By the time you're all done writing your PersonCollection, you've implemented two interfaces and done lots of logic for your MoveNext and Reset methods - especially when all this work was just to say "what's my next item please?" 

This is where the yield statement kicks in. It allows you to skip over implementing MoveNext and Reset and jumps straight to the heart of the matter. In IEnumerable.GetEnumerator - rather than creating a custom enumerator with MoveNext/Reset - just say what the next item should be in the list using the "yield" operator. The C# compiler will take care of creating the rest of the goo for you.

The one pitfall to this is that from the developer perspective it the code for GetEnumerator seems a little strange as you're now returning data instead of returning something of type IEnumerator. All is still right with the world - under the covers C# is generating a series of classes (in fact, an entire state machine) for you. 

Using Yield in Practice
The moral of the story is STOP thinking so hard about it, just use "yield return" the next piece of data in the list. When using the yield statment GetEnumerator's job is to answer foreach's question - "what's my next item please". Walk all the items in your collection and yield return what you want. Don't worry about remembering where you were - that's all part of the generated statemachine goo - it will stop and start the function you write as it pleases in it's MoveNext implementation or whatever (cause we dont really care - we're not suppposed to be thinking, remember?) . =)

At any rate, I realize I might not be the foremost expert on it (you may have noticed from my usage of "statemachine goo" as a technical term), but I hope this helps you think about how you can start using the yield keyword and enumerators in general in your code.

More on yield here

Yield sample from MSDN:

//Copyright (C) Microsoft Corporation. All rights reserved.

using System;

using System.Collections.Generic;

using System.Text;

namespace Yield

{

    class Yield

    {

        public static class NumberList

        {

            // Create an array of integers.

            public static int[] ints = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377 };

            // Define a property that returns only the even numbers.

            public static IEnumerable<int> GetEven()

        {

                // Use yield to return the even numbers in the list.

                foreach (int i in ints)

                    if (i % 2 == 0)

                        yield return i;

            }

            // Define a property that returns only the even numbers.

            public static IEnumerable<int> GetOdd()

            {

                // Use yield to return only the odd numbers.

                foreach (int i in ints)

                    if (i % 2 == 1)

                        yield return i;

            }

        }

        static void Main(string[] args)

        {

            // Display the even numbers.

            Console.WriteLine("Even numbers");

            foreach (int i in NumberList.GetEven())

                Console.WriteLine(i);

            // Display the odd numbers.

            Console.WriteLine("Odd numbers");

            foreach (int i in NumberList.GetOdd())

                Console.WriteLine(i);

        }

    }

}

Comments