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.

List list = 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;
}

Leave a Reply

Your email address will not be published. Required fields are marked *