When i have a code block
static void Main()
{
foreach (int i in YieldDemo.SupplyIntegers())
{
Console.WriteLine(\"{0} is consumed by foreach iteration\
Nope - far from it; I'll write a long-hand version for you... it is too grungy!
Note it also helps if you understand that the foreach
is actually:
using(var iterator = YieldDemo.SupplyIntegers().GetEnumerator()) {
int i;
while(iterator.MoveNext()) {
i = iterator.Current;
Console.WriteLine("{0} is consumed by foreach iteration", i);
}
}
using System;
using System.Collections;
using System.Collections.Generic;
static class Program
{
static void Main()
{
foreach (int i in YieldDemo.SupplyIntegers())
{
Console.WriteLine("{0} is consumed by foreach iteration", i);
}
}
}
class YieldDemo
{
public static IEnumerable<int> SupplyIntegers()
{
return new YieldEnumerable();
}
class YieldEnumerable : IEnumerable<int>
{
public IEnumerator<int> GetEnumerator()
{
return new YieldIterator();
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
class YieldIterator : IEnumerator<int>
{
private int state = 0;
private int value;
public int Current { get { return value; } }
object IEnumerator.Current { get { return Current; } }
void IEnumerator.Reset() { throw new NotSupportedException(); }
void IDisposable.Dispose() { }
public bool MoveNext()
{
switch (state)
{
case 0: value = 1; state = 1; return true;
case 1: value = 2; state = 2; return true;
case 2: value = 3; state = 3; return true;
default: return false;
}
}
}
}
As you can see, it builds a state machine in the iterator, with the state machine progressed by MoveNext
. I've used the pattern with a state
field, as you can see how this would work for more complex iterators.
Importantly:
finally
block (including using
), it goes in the Dispose()
yield return
become a case
(roughly)yield break
becomes a state = -1; return false;
(or similar)The way the C# compiler does this is very complicated, but it makes writing iterators a breeze.
It's just a syntax sugar, .net generates IEnumerator class for you and implements MoveNext, Current and Reset methods, than generates IEnumarable class GetEnumerator of which returns that IEnumerator, you can see that magic classes by .net reflector or ildasm.
Also see here
in short, (while yr waiting for marc's long-hand version) when the compiler sees yield statements, behind the scenes it builds a new instance of a custom class for you that implements an interface called IEnumerator
, which has methods Current()
, and MoveNext()
, and keeps track of where you are currently in the iteration process... In the case above as your example, it would also keep track of the values in the list to be enumerated through.
Simply put, iterator blocks (or methods with yield
statements, if you may) are transformed by the compiler into a compiler-generated class. This class implements IEnumerator
and the yield
statement is transformed into a 'state' for that class.
For instance, this:
yield return 1;
yield return 2;
yield return 3;
might get transformed into something similar to:
switch (state)
{
case 0: goto LABEL_A;
case 1: goto LABEL_B;
case 2: goto LABEL_C;
}
LABEL_A:
return 1;
LABEL_B:
return 2;
LABEL_C:
return 3;
Iterator blocks are can be seen as abstracted state machines. This code will be invoked by IEnumerator
's methods.