Wednesday, September 17, 2008

Enumeration and Iterators




An enumerator is a read-only, forward-only cursor over a sequence of values. An enumerator is an object that either:

  • Implements IEnumerator or IEnumerator

  • Has a method named MoveNext for iterating the sequence, and a property called Current for getting the current element in the sequence

The foreach statement iterates over an enumerable object. An enumerable object is the logical representation of a sequence, and is not itself a cursor, but an object that produces cursors over itself. An enumerable object either:

  • Implements IEnumerable or IEnumerable

  • Has a method named GetEnumerator that returns an enumerator

Iterators

An iterator is a method, property, or indexer that contains one or more yield statements. An iterator must return one of the following four interfaces (otherwise, the compiler will generate an error):

 // Enumerable interfaces
 System.Collections.IEnumerable
 System.Collections.Generic.IEnumerable

 // Enumerator interfaces
 System.Collections.IEnumerator
 System.Collections.Generic.IEnumerator

Iterators that return an enumerator interface tend to be used less often. They're useful when writing a custom collection class: typically, you name the iterator GetEnumerator and have your class implement IEnumerable.

Iterators that return an enumerable interface are more common—and simpler to use because you don't have to write a collection class. The compiler, behind the scenes, writes a private class implementing IEnumerable (as well as IEnumerator).

foreach statement is a consumer of an enumerator, an iterator is a producer of an enumerator. In this example, we use an iterator to return a sequence of Fibonacci numbers (where each number is the sum of the previous two):
using System;
using System.Collections.Generic;

class Test
{
  static void Main()
  {
      foreach (int fib in Fibs(6))
          Console.Write(fib + " ");
      Console.Read();
  }

  static IEnumerable<int> Fibs(int fibCount)
  {
      int prevFib = 1;
      int curFib = 1;
      for (int i = 0; i < fibCount; i++)
      {
       
          int newFib = prevFib + curFib;
          prevFib = curFib;
          curFib = newFib;
          yield return prevFib;
      }
  }
}


Whereas a return statement expresses "Here's the value you asked me to return from this method," a yield return statement expresses "Here's the next element you asked me to yield from this enumerator." On each yield statement, control is returned to the caller, but the callee's state is maintained so that the method can continue executing as soon as the caller enumerates the next element. The lifetime of this state is bound to the enumerator, such that the state can be released when the caller has finished enumerating.


No comments:

Post a Comment