BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

C# Programming
.NET 2.0+

C# Iterators

C# 2.0 introduced iterators. Iterators can be added to a class to allow traversing of a sequence of values with the foreach command. Although possible in earlier language versions, iterators remove the need to manually implement the IEnumerable interface.

IEnumerable and IEnumerator

When using C# with the .NET framework version 1.1, you can create classes that behave like collections or arrays. If you wish to use the foreach loop structure to iterate through the values in your class, you must implement the IEnumerable interface. This interface contains a single method, named GetEnumerator, which returns an enumerator object. It is this object that permits the loop to operate.

The returned enumerator must support the IEnumerator interface. This interface contains a property that returns the currently selected item from the sequence, and some methods to enable the foreach loop to navigate through the list. Each of the interface's members must be implemented, and you must maintain the state of the enumerator correctly. This can lead to a substantial amount of boilerplate code to support a reasonably simple construct.

Iterators

C# 2.0 introduced iterators. An iterator replaces the need to manually create an enumerator, instead allowing the compiler to automatically create the methods and properties of the IEnumerator interface. This leads to a much simpler syntax and a very large reduction in the requirement for boilerplate code.

Implementing Iterators

There are several ways in which to implement an iterator. The choice that you make will depend upon the syntax that you wish to use when using the iterator. In this article we will examine two variations. The first will create an "enumerable class". This is a class that implements the IEnumerable interface, permitting looping using the simplest syntax, eg:

foreach (object o in mySequence) { }

The second example will include an enumerable method that contains the iterator, leading to loops with a slightly different syntax, eg:

foreach (object o in myObject.MySequence()) { }

Creating an Enumerable Class

In the first example we will create a class that can be enumerated directly. This is the ideal choice when you are creating a class that should behave like a collection. To create such a class, it must implement the IEnumerable interface, or the generic IEnumerable<T> version. In this case, we will create a class that behaves as a collection of strings. We will use the generic version of the IEnumerable interface so that the compiler can enforce strict typing for us.

To declare the class, we can use the following code:

public class MyEnumerableClass : IEnumerable<string>
{
}

When we add the iterator, it will return a sequence of string values. These values can be calculated on the fly or retrieved from a data source. In this case we will hold a set of strings in an array. To define the array in the class, add the following declaration:

private string[] _fruitList = new string[]
    { "Apple", "Banana", "Clementine", "Damson", "Elderberry", "Fig", "Grape",
      "Huckleberry", "Indian Prune", "Jujube", "Kiwi", "Lime"};

Adding the GetEnumerator Method

As we are implementing the IEnumerable<T> interface, we must provide a GetEnumerator method. This is the method that will be called when a foreach loop for the class is executed. To add the method, add the following code:

public IEnumerator<string> GetEnumerator()
{
}

The Yield Keyword

The key to C# 2.0 iterators is the yield keyword. Within the iterator block, in this case the GetEnumerator method, the yield return statement is used to return each of the items in the sequence. Often, the yield return statement appears in a loop but it is quite acceptable to provide a series of yield return statements with no loop present, or to create several loops to be executed one after the other.

When the yield return statement is executed, the specified value is returned to the calling statement and the state of the iterator is stored. When the next item in the sequence is requested, the code execution continues from the previous position. It is important to understand that the enumerator method is not executed in full each time.

In our example, the list of fruits will be returned in order. We can therefore use a for-loop to process the array. Add the following loop within the GetEnumerator method. NB: This is a simple example for demonstration purposes only. It is unlikely, though not impossible, that you would need to implement such a simple iterator as the array already supports the IEnumerable interface.

for (int i = 0; i < _fruitList.Length; i++)
{
    yield return _fruitList[i];
}

When a foreach loop is executed against the class, the first iteration of the loop will execute the GetEnumerator method. This will initialise the for-loop before yielding the first result. On the second iteration of the foreach loop, the for-loop will not be re-initialised. The iterator will have stored its own state and will know that it should continue with the second iteration of the for-loop. This process continues until the for-loop is exhausted. The calling foreach loop is then terminated.

Before we can test the new iterator, we need to implement another method. As we decided to implement the IEnumerable<T> interface, we must also add the members of IEnumerable. This means creating a non-generic GetEnumerator method. We could include the same loop and yield statements in this loop. However, rather than repeating ourselves, this method can simply return the results of the existing version:

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

IEnumerator is defined in the System.Collections namespace, so add the following using directive to the code:

using System.Collections;
4 December 2008