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 IEnumerableFibs (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 IEnumerableFoo() { 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 IEnumerableFoo (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 IEnumerableFoo() { 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 IEnumerableFoo() { 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 IEnumerableFibs (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; }