C# And The Little Iterator That Could

A quick rundown of the Iterator keyword and Yield Return in C#

  • An iterator is a C# language feature that helps in writing collections, in the same way the foreach statement helps in consuming collections. An iterator automatically handles the implementation of IEnumerable and IEnumerator or their generic versions.

    The yield return statement causes an element in the source sequence to be returned immediately to the caller before the next element in the source sequence is accessed. Although you write an iterator as a method, the compiler actually translates it into a nested class that is, in effect, a state machine. This class keeps track of the position of the iterator as long the foreach loop on the client code continues.

    Among other languages, iterators are used in C++, C# and other .NET languages, Java, Ruby, and Python. The primary purpose of an iterator is to allow a user to process every element of a container while isolating the user from the internal structure of the container.

    Here is an example:

    public class MyCollection : IEnumerable
    {
    int[] data = { 1, 2, 3 };
    public IEnumerator GetEnumerator()
    {
    foreach (int i in data)
    yield return i;
    }
    }


    Notice that GetEnumerator doesn’t seem to return an enumerator at all. When parsing the yield return statement, the compiler writes a hidden nested enumerator class behind the scenes, and then refactors GetEnumerator to instantiate and return that class. Iterators are powerful and simple, and they are the basis for much of the implementation of LINQ.

    Using this approach, you can also implement the generic interface IEnumerable<T>:

    public class MyGenericCollection : IEnumerable<int>
    {
    int[] data = { 1, 2, 3 };
    public IEnumerator<int> GetEnumerator()
    {
    foreach (int i in data)
    yield return i;
    }
    IEnumerator IEnumerable.GetEnumerator() // Explicit implementation is hidden
    {

    return GetEnumerator();
    }
    }


    Because IEnumerable<T> implements IEnumerable, you have to implement both the generic and the nongeneric versions of GetEnumerator. In accordance with standard practice, the nongeneric version is implemented explicitly. It can simply call the generic GetEnumerator because IEnumerator<T> implements nongeneric IEnumerator.

    The class above would be suitable as a basis from which to write a more sophisticated collection. But if you need no more than a simple IEnumerable<T> implementation, the yield return statement makes it easier.

    Instead of writing a class, you can move the iteration logic into a method returning a generic IEnumerable<T> and let the compiler take care of the rest. An example:

    public class Test
    {
    public static IEnumerable <int> GetSomeIntegers()
    {
    yield return 1;
    yield return 2;
    yield return 3;
    }
    }


    This is the method in action:

    foreach (int i in Test.GetSomeIntegers())
    Console.WriteLine (i);

    Output:
    1
    2
    3

    Iterators Overview:

    • An iterator is a section of code that returns an ordered sequence of values of the same type.
    • An iterator can be used as the body of a method, an operator, or a get accessor.
    • The iterator code uses the yield return statement to return each element in turn. Yield break ends the iteration.
    • Multiple iterators can be implemented on a class. Each iterator must have a unique name just like any class member, and can be invoked by client code in a foreach statement as follows: foreach(int x in SampleClass.Iterator2){}.
    • The return type of an iterator must be IEnumerable, IEnumerator, IEnumerable<T>, or IEnumerator<T>.
    • Iterators are the basis for the deferred execution behavior in LINQ queries.
    • The yield keyword is used to specify the value, or values, that are returned. When the yield return statement is reached, the current location is stored. Execution is restarted from this location the next time that the iterator is called.
    • Iterators are especially useful with collection classes, and provide an easy way to iterate complex data structures such as binary trees.

      • Here is the example code from MSDN:

        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Text;

        namespace Iterators
        {
        public class DaysOfTheWeek : System.Collections.IEnumerable
        {
        string[] days = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" };

        public System.Collections.IEnumerator GetEnumerator()
        {
        for (int i = 0; i < days.Length; i++)
        {
        yield return days[i];
        }
        }
        }

        class TestDaysOfTheWeek
        {
        static void Main()
        {
        // Create an instance of the collection class
        DaysOfTheWeek week =
        new DaysOfTheWeek();

        // Iterate with foreach
        foreach (string day in week)
        {
        System.Console.Write(day + " ");
        }
        }
        }

        }


        We can disassemble this and here is a relevant section of IL code that the compiler generates:

        .property instance object System.Collections.Generic.IEnumerator<System.Object>.Current
        {
        .get instance
        object Iterators.DaysOfTheWeek/<GetEnumerator>d__0::System.Collections.Generic.IEnumerator<System.Object>.get_Current()
        }

        .property instance
        object System.Collections.IEnumerator.Current
        {
        .get instance
        object Iterators.DaysOfTheWeek/<GetEnumerator>d__0::System.Collections.IEnumerator.get_Current()
        }

        // note t
        hat the compiler has created a state field:
        .field
        private int32 <>1__state

        .field
        private object <>2__current

        .field
        public class Iterators.DaysOfTheWeek <>4__this

        .field
        public int32 <i>5__1

        }

        .method
        private hidebysig newslot virtual final instance bool MoveNext() cil managed
        {
        .
        override [mscorlib]System.Collections.IEnumerator::MoveNext
        .maxstack 3
        .locals init (
        [0]
        bool CS$1$0000,
        [1] int32 CS$4$0001,
        [2]
        bool CS$4$0002)
        L_0000: ldarg.0
        L_0001: ldfld int32 Iterators.DaysOfTheWeek/<
        GetEnumerator>d__0::<>1__state
        L_0006: stloc.1
        L_0007: ldloc.1
        L_0008:
        switch (L_0019, L_0017)
        L_0015: br.s L_001b
        L_0017: br.s L_0052
        L_0019: br.s L_001d
        L_001b: br.s L_0082
        L_001d: ldarg.0
        L_001e: ldc.i4.m1
        L_001f: stfld int32 Iterators.DaysOfTheWeek/<
        GetEnumerator>d__0::<>1__state
        L_0024: nop
        L_0025: ldarg.0
        L_0026: ldc.i4.0
        L_0027: stfld int32 Iterators.DaysOfTheWeek/<
        GetEnumerator>d__0::<i>5__1
        L_002c: br.s L_0068
        L_002e: nop
        L_002f: ldarg.0
        L_0030: ldarg.0
        L_0031: ldfld
        class Iterators.DaysOfTheWeek Iterators.DaysOfTheWeek/<GetEnumerator>d__0::<>4__this
        L_0036: ldfld
        string[] Iterators.DaysOfTheWeek::days
        L_003b: ldarg.0
        L_003c: ldfld int32 Iterators.DaysOfTheWeek/<
        GetEnumerator>d__0::<i>5__1
        L_0041: ldelem.
        ref
        L_0042: stfld
        object Iterators.DaysOfTheWeek/<GetEnumerator>d__0::<>2__current
        L_0047: ldarg.0
        L_0048: ldc.i4.1
        L_0049: stfld int32 Iterators.DaysOfTheWeek/<
        GetEnumerator>d__0::<>1__state
        L_004e: ldc.i4.1
        L_004f: stloc.0
        L_0050: br.s L_0086
        L_0052: ldarg.0
        L_0053: ldc.i4.m1
        L_0054: stfld int32 Iterators.DaysOfTheWeek/<
        GetEnumerator>d__0::<>1__state
        L_0059: nop
        L_005a: ldarg.0
        L_005b: dup
        L_005c: ldfld int32 Iterators.DaysOfTheWeek/<
        GetEnumerator>d__0::<i>5__1
        L_0061: ldc.i4.1
        L_0062: add
        L_0063: stfld int32 Iterators.DaysOfTheWeek/<
        GetEnumerator>d__0::<i>5__1
        L_0068: ldarg.0
        L_0069: ldfld int32 Iterators.DaysOfTheWeek/<
        GetEnumerator>d__0::<i>5__1
        L_006e: ldarg.0
        L_006f: ldfld
        class Iterators.DaysOfTheWeek Iterators.DaysOfTheWeek/<GetEnumerator>d__0::<>4__this
        L_0074: ldfld
        string[] Iterators.DaysOfTheWeek::days
        L_0079: ldlen
        L_007a: conv.i4
        L_007b: clt
        L_007d: stloc.2
        L_007e: ldloc.2
        L_007f: brtrue.s L_002e
        L_0081: nop
        L_0082: ldc.i4.0
        L_0083: stloc.0
        L_0084: br.s L_0086
        L_0086: ldloc.0
        L_0087: ret
        }


        Note that by simply using the yield keyword, the compiler is doing a lot of work for us behind the scenes!
By Peter Bromberg   Popularity  (3540 Views)