Using Iterators


As the preceding example shows, it is not difficult to implement IEnumerator and IEnumerable. However, C# 2.0 makes doing so even easier through the use of an iterator. An iterator is a method, operator, or accessor that returns the members of a set of objects, one member at a time, from start to finish. For example, assuming some array that has five elements, then an iterator for that array will return those five elements, one at a time. By implementing an iterator, you make it possible for an object of a class to be used in a foreach loop.

Let’s begin with an example of a simple iterator. The following program is a modified version of the preceding program that uses an iterator rather than explicitly implementing IEnumerator and IEnumerable:

 // A simple example of an iterator. using System; using System.Collections; class MyClass {   char[] chrs = { 'A', 'B', 'C', 'D' };   // This iterator returns the characters   // in the chrs array.   public IEnumerator GetEnumerator() {     foreach(char ch in chrs)       yield return ch;   } } class ItrDemo {   public static void Main() {     MyClass mc = new MyClass();     foreach(char ch in mc)       Console.Write(ch + " ");     Console.WriteLine();   } }

The output is shown here:

 A B C D

As you can see, the contents of mc.chrs were enumerated.

Let’s examine this program carefully. First, notice that MyClass does not specify either IEnumerator or IEnumerable as implemented interfaces. When creating an iterator, the compiler automatically implements these interfaces. Second, pay special attention to the GetEnumerator( ) method, which is shown again here for your convenience:

 // This iterator returns the characters // in the chrs array. public IEnumerator GetEnumerator() {   foreach(char ch in chrs)     yield return ch; }

This is the iterator for MyClass. Notice that it implicitly implements the GetEnumerator( ) method defined by IEnumerable. Now, look at the body of the method. It contains a foreach loop that returns the elements in chrs. It does this through the use of a yield return statement. The yield return statement returns the next object in the collection, which in this case is the next character in chrs. Thus, when an object of type MyClass is enumerated, each call to Current obtains the next element in chrs through the yield return statement. This feature enables mc (a MyClass object) to be used within the foreach loop inside Main( ).

The term yield is a contextual keyword in the C# language. This means that it only has special meaning inside an iterator block. Outside of an iterator, yield can be used like any other identifier.

There are two ways to create an iterator. First, you can simply include a method called GetEnumerator( ) in your class. This method must return an IEnumerator object and, of course, it must also enumerate the contents of the object. Any class that defines such a method can be used in a foreach loop. This is the approach used by the preceding program. The second approach creates a named iterator and it is described later in this section.

One important point to understand is that an iterator does not need to be backed by an array or other type of collection. It simply must return the next element in a group of elements. This means that the elements can be dynamically constructed using an algorithm. For example, here is a version of the previous program that returns all uppercase letters in the alphabet. Instead of using an array, it generates the letters using a for loop.

 // Iterated values can be dynamically constructed. using System; using System.Collections; class MyClass {   char ch = 'A';   // This iterator returns the letters   // of the alphabet.   public IEnumerator GetEnumerator() {     for(int i=0; i < 26; i++)       yield return (char) (ch + i);   } } class ItrDemo2 {   public static void Main() {     MyClass mc = new MyClass();     foreach(char ch in mc)       Console.Write(ch + " ");     Console.WriteLine();   } }

The output is shown here:

 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

Stopping an Iterator

You can stop an iterator early by using this form of the yield statement:

 yield break;

When this statement executes, the iterator signals that the end of the collection has been reached, which effectively stops the iterator.

The following program modifies the preceding program so that it displays only the first ten letters in the alphabet:

 // Use yield break. using System; using System.Collections; class MyClass {   char ch = 'A';   // This iterator returns the first 10   // letters of the alphabet.   public IEnumerator GetEnumerator() {     for(int i=0; i < 26; i++) {       if(i == 10) yield break; // stop iterator early       yield return (char) (ch + i);     }   } } class ItrDemo3 {   public static void Main() {     MyClass mc = new MyClass();     foreach(char ch in mc)       Console.Write(ch + " ");     Console.WriteLine();   } } 

The output is shown here:

 A B C D E F G H I J

Using Multiple yield Directives

You can have more than one yield statement in an iterator. However, each yield must return the next element in the collection. For example, consider this program:

 // Multiple yield statements are allowed. using System; using System.Collections; class MyClass {   // This iterator returns the letters   // A, B, C, D and E.   public IEnumerator GetEnumerator() {     yield return 'A';     yield return 'B';     yield return 'C';     yield return 'D';     yield return 'E';   } } class ItrDemo5 {   public static void Main() {     MyClass mc = new MyClass();     foreach(char ch in mc)       Console.Write(ch + " ");     Console.WriteLine();   } }

The output is shown here:

 A B C D E

Inside GetEnumerator( ), five yield statements occur. The important thing to understand is that they are executed one at a time, in order, each time another element in the collection is obtained. Thus, each time through the foreach loop in Main( ), one character is returned.

Creating a Named Iterator

Although the preceding examples have shown the easiest way to implement an iterator, there is an alternative. As explained earlier, there are two ways to implement an iterator. The first is to create a GetEnumerator( ) method that returns a reference to an IEnumerator object.

This is the approach used by the preceding examples. The second way is to create a method, operator, or accessor that returns a reference to an IEnumerable object. This creates a named iterator, which your code will use to supply the iterator. A named iterator is a method with the following general form:

 public IEnumerable itr-name(param-list) {   // ...   yield return obj; }

Here, itr-name is the name of the method, param-list specifies zero or more parameters that will be passed to the iterator method, and obj is the next object returned by the iterator. Once you have created a named iterator, you can use it anywhere that an iterator is needed. For example, you can use the named iterator to control a foreach loop.

Named iterators are very useful in some circumstances because they allow you to pass arguments to the iterator that control what elements are obtained. For example, you might pass the iterator the beginning and ending points of a range of elements to iterate. This form of iterator can also be overloaded, further adding to its flexibility. The following program illustrates two ways that a named iterator can be used to obtain elements. One enumerates a range of elements given the endpoints. The other enumerates the elements, beginning at the start of the sequence and ending at the specified stopping point.

 // Use named iterators. using System; using System.Collections; class MyClass {   char ch = 'A';   // This iterator returns the letters   // of the alphabet, beginning at A and   // stopping at the specified starting point.   public IEnumerable MyItr(int end) {     for(int i=0; i < end; i++)       yield return (char) (ch + i);   }   // This iterator returns the specified   // range of letters   public IEnumerable MyItr(int begin, int end) {     for(int i=begin; i < end; i++)       yield return (char) (ch + i);   } } class ItrDemo4 {   public static void Main() {     MyClass mc = new MyClass();     Console.WriteLine("Iterate the first 7 letters:");     foreach(char ch in mc.MyItr(7))       Console.Write(ch + " ");     Console.WriteLine("\n");     Console.WriteLine("Iterate letters from F to L:");     foreach(char ch in mc.MyItr(5, 12))       Console.Write(ch + " ");     Console.WriteLine();   } }

The output is shown here:

 Iterate the first 7 letters: A B C D E F G Iterate letters from F to L: F G H I J K L

Create a Generic Iterator

The preceding examples of iterators have been non-generic, but it is, of course, also possible to create generic iterators. Doing so is quite easy: simply return an object of the generic IEnumerator<T> or IEnumerable<T> type. Here is an example that creates a generic iterator based on the IEnumerator pattern:

 // A simple example of a generic iterator. using System; using System.Collections.Generic; class MyClass<T> {   T[] array;   public MyClass(T[] a) {     array = a;   }   // This iterator returns the characters   // in the chrs array.   public IEnumerator<T> GetEnumerator() {     foreach(T obj in array)       yield return obj;   } } class GenericItrDemo {   public static void Main() {     int[] nums = { 4, 3, 6, 4, 7, 9 };     MyClass<int> mc = new MyClass<int>(nums);     foreach(int x in mc)       Console.Write(x + " ");     Console.WriteLine();    bool[] bVals = { true, true, false, true };     MyClass<bool> mc2 = new MyClass<bool>(bVals);     foreach(bool b in mc2)       Console.Write(b + " ");     Console.WriteLine();   } }

The output is shown here:

 4 3 6 4 7 9 True True False True

In this example, the array containing the objects to be iterated is passed to MyClass( ) through its constructor. The type of the array is specified as a type argument to MyClass. The GetEnumerator( ) method operates on data of type T and returns an IEnumerator<T> enumerator. Thus, the iterator defined by MyClass can enumerate any type of data.




C# 2.0(c) The Complete Reference
C# 2.0: The Complete Reference (Complete Reference Series)
ISBN: 0072262095
EAN: 2147483647
Year: 2006
Pages: 300

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net