BlackWasp
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;

Executing the Iterator

Now that the iterators are defined, we can traverse the class' values with a simple foreach loop. In the following sample, a new object is created and used as the target for a loop. Try executing the sample to see the results. It is also worthwhile stepping through the code using the Visual Studio debugger in order to see the operation of the iterator.

MyEnumerableClass mec = new MyEnumerableClass();

foreach (string fruit in mec)
    Console.WriteLine(fruit);

/* OUTPUT

Apple
Banana
Clementine
Damson
Elderberry
Fig
Grape
Huckleberry
Indian Prune
Jujube
Kiwi
Lime

*/

Creating an Enumerable Method

If you wish to create more than one iterator for a single class, or you would prefer to access the iterator using a method or a property, that member must return an IEnumerable or IEnumerable<T> object. The internal implementation of the method can be identical to that of the previous example.

To demonstrate, we can add a method that implements an iterator that returns the fruit names in reverse order:

public IEnumerable<string> Reverse()
{
    for (int i = _fruitList.Length - 1; i >= 0; i--)
    {
        yield return _fruitList[i];
    }
}

To loop through the sequence, the method is called within the foreach loop:

foreach (string fruit in mec.Reverse())
{
    Console.WriteLine(fruit);
}

Using Yield Break

When using yield return statements, the iterator continues to execute until all of the yield statements have been processed and the method ends normally. This signifies the termination of the iterator and causes any foreach loop to end. However, sometimes you will want to stop the enumerator earlier, perhaps in response to a condition being met. When this occurs, you can use the yield break statement.

To demonstrate, add the following method to the MyEnumerableClass class. This method accepts a parameter, which may contain the name of a fruit. If the name is found whilst looping through the array, it is retuned. However, on the next iteration the yield break statement is encountered so the loop ends early.

public IEnumerable<string> Limited(string lastItem)
{
    for (int i = 0; i < _fruitList.Length; i++)
    {
        yield return _fruitList[i];

        if (_fruitList[i] == lastItem)
        {
            yield break;
        }
    }
}

Try executing a foreach loop for this method, passing a fruit name to see the loop exiting before all of the fruits are returned.

Link to this Page4 December 2008
TwitterTwitter RSS Feed RSS