Enumeration and Iterators
Enumeration
Read-only, forward-only cursor over a sequence of values.
Enumerator object implements one of the interfaces:
- System.Collections.IEnumerator
- System.Collections.Generics.IEnumerator<T>
Enumerable object:
- Iterates using foreach statement
- Implements IEnumerable or IEnumerable<T>
- Has a method named GetEnumerator that returns an enumerator
The enumeration pattern:
Implements IEnumerator or IEnumerator<T>
class Enumerator
{
public IteratorVariableType Current { get { ... }}
public bool MoveNext() { ... }
}
Implements IEnumerable or IEnumerable<T>
class Enumerable
{
public Enumerator GetEnumerator() { ... }
}
// High-level way of iterating through the characters in the word “beer”:
foreach (char c in "beer")
Console.WriteLine (c);
// Low-level way of iterating through the same characters:
using (var enumerator = "beer".GetEnumerator())
while (enumerator.MoveNext())
{
var element = enumerator.Current;
Console.WriteLine (element);
}
Collection Initializers
Enumerable object can be instantiated and populated in a single step.
Enumerable object implements the System.Collections.IEnumerable interface and it has Add method with appropriate number of parameters for the call.
Listlist = new List {1, 2, 3}; // Equivalent to: { List list = new List (); list.Add (1); list.Add (2); list.Add (3); }
With dictionaries
var dict1 = new Dictionary() { { 5, "five" }, { 10, "ten" } }; //you can use the indexer var dict2 = new Dictionary () { [3] = "three", [10] = "ten" };
Iterators
Whereas a foreach statement is a consumer of an enumerator, an iterator is a producer of an enumerator.
static void Main()
{
foreach (int fib in Fibs(6))
Console.Write (fib + " ");
}
static IEnumerable Fibs (int fibCount)
{
for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
{
yield return prevFib;
int newFib = prevFib+curFib;
prevFib = curFib;
curFib = newFib;
}
}
Iterator Semantics
An iterator is a method, property or indexer that contains one or more yield statements. It must return one of the following interfaces:
// Enumerable interfaces System.Collections.IEnumerable System.Collections.Generic.IEnumerable// Enumerator interfaces System.Collections.IEnumerator System.Collections.Generic.IEnumerator
An iterator has different semantics, depending on whether it returns an enumerable interface or an enumerator interface.
Multiple yield statment example:
static void Main()
{
foreach (string s in Foo())
Console.WriteLine(s); // Prints "One","Two","Three"
}
static IEnumerable Foo()
{
yield return "One";
yield return "Two";
yield return "Three";
}
yield break
The yield break statement indicates that the iterator block should exit early, without returning more elements
static void Main()
{
foreach (string s in Foo (true))
Console.WriteLine(s);
}
static IEnumerable Foo (bool breakEarly)
{
yield return "One";
yield return "Two";
if (breakEarly)
yield break;
yield return "Three";
}
Iterators and try/catch/finally blocks
A yield return statement cannot appear in a try block that has a catch clause. It can also not appear in a catch or finally block
static void Main()
{
foreach (string s in Foo())
Console.WriteLine(s);
}
static IEnumerable Foo()
{
try { yield return "One"; } // Illegal
catch { /*...*/ }
}
It is possible to yield within a try block that has only a finally block.
static void Main()
{
foreach (string s in Foo()) s.Dump();
Console.WriteLine();
foreach (string s in Foo())
{
("First element is " + s).Dump();
break;
}
}
static IEnumerable Foo()
{
try
{
yield return "One";
yield return "Two";
yield return "Three";
}
finally { "Finally".Dump(); }
}
The code in the finally block executes when the consuming enumerator reaches the end of the sequence or is disposed.
A foreach statement implcitly disposes the enumerator if you break early.
For explicit enumerators, a trap is to abandon enumeration early without disposing it. This can be avoided with the using statement.
string firstElement = null; var sequence = Foo(); using (var enumerator = sequence.GetEnumerator()) if (enumerator.MoveNext()) firstElement = enumerator.Current;
Composing Sequences
Iterators are highly composable.
static void Main()
{
foreach (int fib in EvenNumbersOnly (Fibs(6)))
Console.WriteLine (fib);
}
static IEnumerable Fibs (int fibCount)
{
for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
{
yield return prevFib;
int newFib = prevFib+curFib;
prevFib = curFib;
curFib = newFib;
}
}
static IEnumerable EvenNumbersOnly (IEnumerable sequence)
{
foreach (int x in sequence)
if ((x % 2) == 0)
yield return x;
}