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 that 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!