Iterators
An iterator is a block of code that supplies all the values to be used in a foreach loop. A class that represents a collection can implement the System.Collections.IEnumerable interface. This interface requires an implementation for the GetEnumerator()method which returns an IEnumerator interface. The IEnumerator interface has a Current property which contains the current value that was returned by the iterator. It also has a MoveNext() method which moves the Current property to the next item and returns false if there is no more item left. The Reset() method returns the iterator back to the first item. The IEnumerator interface is implemented by different types of collections in the .NET library and this includes arrays which is why you can use the foreach loop on them. Supposed we have a code like this which reads every element of an array using a foreach loop.
int[] numbers = { 1, 2, 3, 4, 5 };
foreach(int n in numbers)
{
Console.WriteLine(n);
}
To better understand iterators, let’s translate the above foreach loop into a call to an array’s GetEnumerator() method.
int[] numbers = { 1, 2, 3, 4, 5 };
IEnumerator iterator = numbers.GetEnumerator();
while(iterator.MoveNext())
{
Console.WriteLine(iterator.Current);
}
As you can see, we first retrieve the array’s iterator using the GetEnumerator() method which returns an IEnumerator interface. We used this iterator in a while loop and called the MoveNext() method. The MoveNext() method retrieves the first element of a collection such as an array and if it is successful in retrieving, the method will return true. It will then retrieve the second element on the next call and so on until it reaches the end of the array where it will return false because there is no more to retrieve. The retrieved value of the element can be accessed using the IEnumerator.Current property.
Now that you know how an iterator works and its major contribution when using iterating values from collections, let’s consider creating our own iterators. Using an iterator requires a yield return statement. A yield return statement is different from an ordinary return statement. One visible difference is the use of the keyword yield before the return keyword. Consider the following example:
using System;
using System.Collections;
namespace IteratorsDemo
{
class Program
{
public static IEnumerable GetMessages()
{
yield return "Message 1";
yield return "Message 2";
yield return "Message 3";
}
public static void Main()
{
foreach (string message in GetMessages())
{
Console.WriteLine(message);
}
}
}
}
Example 1 – A Simple Iterator
Message 1 Message 2 Message 3
The GetMessages() method returns an IEnumerable object which in turn contains a definition for a GetEnumerator() method. The yield return statement brings the value it will return to the variable that will hold each of the values in a foreach loop. The first yield return statement in the method is returned to the foreach loop inside our Main() method. When the foreach loop calls again the GetMessages() method, then the next yield return statement is returned to it. This continues until no more yield returnstatement is found. You can interrupt the returning of values from the method by using the yield break statement.
public static IEnumerable GetMessages()
{
yield return "Message 1";
yield return "Message 2";
yield break;
yield return "Message 3";
}
Creating Our Own Iterator
Let’s take a look an example of using an iterator by creating a new class which contains an ArrayList field that will hold some values. We will then create an iterator which will provide a foreach loop with the values from the ArrayList field.
using System.Collections;
using System;
namespace IteratorsDemo2
{
public class Names : IEnumerable
{
private ArrayList innerList;
public Names(params object[] names)
{
innerList = new ArrayList();
foreach (object n in names)
{
innerList.Add(n);
}
}
public IEnumerator GetEnumerator()
{
foreach (object n in innerList)
{
yield return n.ToString();
}
}
}
public class Program
{
public static void Main()
{
Names nameList = new Names("John", "Mark", "Lawrence", "Michael", "Steven");
foreach (string name in nameList)
{
Console.WriteLine(name);
}
}
}
}
Example 2 – User Defined Iterator
We created a collection class named Names which will hold a list of names. The definition of the class in line 6 shows that we did not implement the CollectionBase class. This is because the CollectionBase class implements the IEnumerable interface and already has an implementation of the GetEnumerator() method. We are creating our collection class from scratch and we will define our own iterator. Our class simply implements the IEnumerable interface which now requires our class to have an implementation of the GetEnumerator() method. Our own iterator is defined in lines 20-26. Inside it, we iterate through each of the value from the innerList field. Each value is converted to string and then yield returned to the caller. Lines 35-38 shows our own iterator in work. Since the yield returns in our iterator convert each value to string, we can simply use string as the type of the range variable of the foreach loop. When the next value will be retrieved from the iterator via the foreach loop in our Main() method, the foreach loop inside the iterator goes to the next iteration and yield return the next value from the innerList. Without the iterator, we won’t be able to use foreach loop for our Names class.
Creating our own iterator gives us a great control on the behavior of the foreach loop when dealing with our class. For example, we can edit our iterator to only return names starting with letter M.
public IEnumerator GetEnumerator()
{
foreach (object n in innerList)
{
if (n.ToString().StartsWith("M"))
yield return n.ToString();
}
}
We used the StartsWith() method of the System.String class to determine if a name starts with letter M. If the names start with letter M, then we yield return it. If not, then it is ignored and then next name in the innerList is inspected. With our modified GetEnumerator() method, when you use a foreach loop on an instance of the Names class, only names starting with letter M will be retrieved.
Even if you can do the above technique of modifying GetEnumerator() method, it would be more practical to just define a separate method for retrieving names starting with a specified letter.
public IEnumerable GetNamesStartingWith(string letter)
{
foreach (object n in innerList)
{
if (n.ToString().StartsWith(letter))
yield return n.ToString();
}
}
We now have a more flexible iterator that you can use to retrieve names which start with a specified letter or substring. Note that we used IEnumerable instead of IEnumerator. The rule is use IEnumerable for all iterators except the GetEnumerator() method which is used as default by the foreach loop. The IEnumerable already has a GetEnumerator() method. When you call this iterator, you need to modify our foreach loop in lines 35-38.
foreach (string name in nameList.GetNamesStartingWith("M"))
{
Console.WriteLine(name);
}
Improving Our Animals Dictionary Class
A more practical use of an iterator is applying it to a collection or dictionary class. For example, the dictionary class we made in the last lesson uses DictionaryEntry as the type of variable that will store each of the elements inside a foreach loop. By using an iterator, we can use Animal as the type of the class and save as a little casting. The following code demonstrates this to you.
using System.Collections;
using System;
namespace IteratorsDemo3
{
public class Animal
{
public string Name { get; set; }
public int Age { get; set; }
public double Height { get; set; }
public Animal(string name, int age, double height)
{
Name = name;
Age = age;
Height = height;
}
}
public class Animals : DictionaryBase
{
public void Add(string key, Animal newAnimalnewAnimal)
{
Dictionary.Add(key, newAnimalnewAnimal);
}
public void Remove(string key)
{
Dictionary.Remove(key);
}
public Animal this[string key]
{
get { return (Animal)Dictionary[key]; }
set { Dictionary[key] = value; }
}
public new IEnumerator GetEnumerator()
{
foreach (object animal in Dictionary.Values)
{
yield return (Animal)animal;
}
}
}
public class Program
{
public static void Main()
{
Animals animalDictionary = new Animals();
animalDictionary.Add("Animal1", new Animal("John", 10, 100));
animalDictionary.Add("Animal2", new Animal("Sussy", 5, 10));
animalDictionary.Add("Animal3", new Animal("Frank", 3, 5));
animalDictionary.Add("Animal4", new Animal("Mark", 7, 15));
foreach (Animal animal in animalDictionary)
{
Console.WriteLine(animal.Name);
}
}
}
}
Example 3 – Adding an Iterator to a Custom Dictionary
Lines 38-44 defines an iterator for our Animals dictionary class. The DictionaryBase class already implements the IEnumerableinterface which has the GetEnumerator() method that is used for getting values from a collection using a foreach loop. We implement our own GetEnumerator() method. Notice that it has a return type of IEnumerator.The new keyword simply indicates that the program should use this version of GetEnumerator() instead of the one already defined in the DictionaryBase. Inside the method, we use a foreach loop to cast and yield return each of the Animal objects from the Values properties. Recall that without an iterator, using a foreach loop for our dictionary class looks like this:
foreach (DictionaryEntry animal in animalDictionary)
{
Console.WriteLine((animal.Value as Animal).Name);
}
With the new iterator defined in our dictionary class, you can now use the type of each element in a foreach loop without using the DictionaryEntry class.
foreach (Animal animal in animalDictionary)
{
Console.WriteLine(animal.Name);
}
Each iteration of the above foreach loop triggers an iteration of the foreach loop inside our iterator and each loop executes a yield return statement.