AddThis Social Bookmark Button

Print

C# Iterators

by Jesse Liberty
06/07/2004

If you are creating a class that looks and behaves like a collection, it is handy to allow your users to iterate through the members of your collection with the foreach statement. This is easier to do in C# 2.0 than it is in 1.1. To illustrate, we'll start by adding iteration to a simple collection in a 1.1 example (adapted from an example used in Programming C#, 3rd Edition) and then we'll modify that example to use the new Iterator construct in C# 2.0.

To begin, we'll create a simplified List Box, concentrating on the fact that a list box acts as a collection of strings, and ignoring the List Box's user interface. (Note: The complete source for the examples in this article are available on my web site. Click on Books and then click on Articles & Publications.)

We'll keep the ListBox class quite simple, it will have an array of eight strings and an integer that will keep track of how many strings have been added to the array. The constructor will initialize the array and populate it with strings passed in as arguments:

public ListBox(params string[] initialStrings)
{
  // allocate space for the strings
  strings = new String[8]; 
  
  // copy the strings passed in to the constructor
  foreach (string s in initialStrings)
  {
    strings[ctr++] = s;
  }
}

In addition, the ListBox class will have an Add method (to add a single string) and a method that returns the number of strings already held in the array:

// add a string to the collection (increment the count)
public void Add(string theString)
{
  strings[ctr] = theString;  // add the string
  ctr++;
}

// publish how many strings you hold
public int GetNumEntries()
{
  return ctr;
}

Note that in a real program, you'd probably use an ArrayList rather than a fixed-size array; thus in this simplified example there is no error checking for going beyond the bounds of the array.

Since the ListBox is, in some sense, a collection, it would be convenient to be able to retrieve all the strings in the list box using the foreach loop normally used on collections. That is, you'd like to to be able to write code like this:

ListBox lb = new ListBox();
foreach (string s in lb)
{ 
  // do something with the string 
}

to be used in a foreach loop. However, your ListBox class must implement IEnumerable:

public class ListBox : IEnumerable
{

The IEnumerable interface mandates only one method: GetEnumerator, which must return an object that implements the IEnumerator interface. You will want to return an object that not only implements IEnumerator, but which knows how to enumerate a ListBox object. You'll create a ListBoxEnumerator (described below):

public IEnumerator GetEnumerator()
{
  return new ListBoxEnumerator();
}

Now ListBox can be used in a foreach loop:

ListBox lbt = 
  new ListBox("Hello", "World");

// add a few strings
lbt.Add("Who");
lbt.Add("Is");
lbt.Add("John");
lbt.Add("Galt");
foreach (string s in lbt)
{
  Console.WriteLine("Value: {0}", s);
}

The list box is instantiated and initialized with two strings, and four more strings are added. The foreach loop takes the instance of the list box and iterates over it, returning each string in turn. The output is:

Hello
World
Who
Is
John
Galt
Programming C#

Related Reading

Programming C#
By Jesse Liberty

Implementing IEnumerator

Note that ListBoxEnumerator must not only implement the IEnumerator interface, it must have special knowledge about the ListBox class; specifically, it must be able to reach into the ListBox's string array to iterate through the strings it contains. This relationship between the IEnumerable class and its associated IEnumerator class can be a bit tricky. The best way to implement the IEnumerator class is to create a nested class within the IEnumerable class:


public class ListBox : IEnumerable
{
  // private nested implementation of ListBoxEnumerator
  private class ListBoxEnumerator : IEnumerator
  {
    // code for nested enumerator here...
  }
  // code for ListBox here...
}

Note that the ListBoxEnumerator requires a reference to the ListBox class within which it is nested. You'll pass that in through the ListBoxEnumerator constructor.

To implement the IEnumerator interface, the ListBoxEnumerator requires two methods: MoveNext and Reset, as well as one Property: Current. The job of these methods and this property is to create a state machine enabling you to know at any time which element in the ListBox is the current element, and to retrieve that element.

In this case, the state machine is maintained by keeping an index value indicating which string is the current one, and you will return the current string by indexing into the outer class's collection of strings. To accomplish this, you'll need a member variable to hold a reference to the outer ListBox object, and an integer to hold the current index.


private ListBox lbt;
private int index;

Each time Reset is called, the index is set to -1.

public void Reset()
{
  index = -1;
}

Each time MoveNext is called, the array held in the outer class is checked to see if it is at the end; if so, the method returns false. If there are more objects in the collection, however, the index is incremented and the method returns true.

public bool MoveNext()
{
  index++;
  if (index >= lbt.strings.Length)
  {
    return false;
  }
  else
  {
    return true;
  }
}

Finally, if MoveNext returns true, the foreach loop will call the Current property. The implementation of Current for the ListBoxEnumerator is to index into the collection in the outer (ListBox) class, and return the object found there (in this case, a string). Note that an object is returned because the signature of the Current property is dictated by the IEnumerator interface.

public object Current
{
  get
  {
    return(lbt[index]);
  }
}

In 1.1 every class that wants to be iterated over by a foreach loop needs to implement the IEnumerable interface, and thus must create a class that imlements IEnumerator. Worst of all, there is no type safety on the values returned by the enumerator; remember, the Current property returns an object; it is simply assumed that what you return will match up with what is expected by the foreach loop.

C# 2.0 to the Rescue

With C# 2.0 these problems melt away like snow in late May. In the 2.0 version of this example I rewrite the previous listing using two features new to C# 2.0: Generics, covered in my previous column, and Iterators.

To get started, I redefine the ListBox to implement IEnumerable of String:

public class ListBox : IEnumerable<string>
{

This specifies both that the class can be used in a foreach loop, and also that the values that will be iterated over will be strings.

Now, toss away the entire nested class from the previous example and replace the GetEnumerator method with this:

public IEnumerator<string> GetEnumerator()
{
   foreach (string s in strings)
   {
      yield return s;
   }
}

The GetEnumerator method makes use of the new yield statement. A yield statement returns an expression. The yield statement appears only inside an iterator block and returns the value that is expected by the calling foreach statement. That is, each call to GetEnumerator will yield the next string in the collection; all the state management is done for you!

That's it. You're done; there is no need to implement your own enumerator for each type, no need to create a nested class. You've eliminated nearly 30 lines of code and greatly simplified your program. The program continues to run as intended, but none of the state management is your responsibility; it is all done for you. Further, the values returned by the iterator are guaranteed to be strings, and if you decide to return other types, you can modify the IEnumerable generic statement and the IEnumerator generic statement to reflect the new type(s).

More About Yield

As a side note to the above, it is worth remarking that you can, in fact, yield more than one value in a yield block. Thus, the following is perfectly legal C#:

public IEnumerator GetEnumerator()
{
  yield return "Who";
  yield return " is";
  yield return "John Galt?";
}

Assuming that code were in a class named foo, you could then write:

foreach ( string s in new foo )
{
   Console.Write(s);
}

The result would be:

Who is John Galt?

If you stop and think about it, that is what the earlier code is doing as well; it is going through its own foreach loop and yielding up each string it finds.

Jesse Liberty is a senior program manager for Microsoft Silverlight where he is responsible for the creation of tutorials, videos and other content to facilitate the learning and use of Silverlight. Jesse is well known in the industry in part because of his many bestselling books, including O'Reilly Media's Programming .NET 3.5, Programming C# 3.0, Learning ASP.NET with AJAX and the soon to be published Programming Silverlight.


Return to ONDotnet.com