The foreach
keyword allows sequences and collections to be looped over without worrying about off-by-1 bugs. It achieves this by way of the IEnumerable
and IEnumerator
interfaces and their generic counterparts.
Producing these sequences required an additional class to act as the iterator keeping track of the location within the sequence so that two different foreach
operations upon the same source do not interfere with each other.
C# 2.0 brings two new operations in order to simplify producing these enumerable sequences. The compiler does this by generating a state machine that returns control to the consumer but maintains the state of the method emitting the sequence so it can be called again when the next item in the sequence is requested.
Each method returns control to the consumer however;
yield return x
- also supplies x as the next item in the sequenceyield break
- indicates the sequence is now complete and there are no more items
Code
class RangeEnumerable : IEnumerable<int>
{
int lower, upper;
public RangeEnumerable(int lower, int upper)
{
this.lower = lower;
this.upper = upper;
}
public IEnumerator<int> GetEnumerator()
{
int current = lower;
while (current <= upper)
yield return current++;
}
}
class RangeEnumerable : IEnumerable<int>
{
int lower, upper;
public RangeEnumerable(int lower, int upper)
{
this.lower = lower;
this.upper = upper;
}
public IEnumerator<int> GetEnumerator()
{
return new RangeEnumerator(lower, upper);
}
}
class RangeEnumerator : IEnumerator<int>
{
int lower, upper, current;
bool started;
public RangeEnumerator(int lower, int upper)
{
this.lower = lower;
this.upper = upper;
current = lower;
}
public int Current { get { return current; } }
public bool MoveNext()
{
if (!started)
started = true;
else
current++;
return current <= upper;
}
public void Reset()
{
current = lower;
started = false;
}
// Dispose and non-generic IEnumerable omitted
}