C# .NET Iterator Examples : Dual Iteration
This example iterates two collections simultaneously:
|
using System; using System.Collections.Generic;
namespace Examples.Iterators { public class Program { public static void Main() { MyClass obj = new MyClass(); foreach (int item in obj) { Console.Write(item); }
Console.ReadKey(); } }
public class MyClass {
private int[] list1 = new int[] { 0, 2, 4, 6, 8 }; private int[] list2 = new int[] { 1, 3, 5, 7, 9 };
public IEnumerator<int> GetEnumerator() { for (int index = 0; index < 5; ++index) { yield return list1[index]; yield return list2[index]; } }
} } |
The preceding code alternates between yielding list1 and list2. As the iteration moves between collections, the even and odd numbers are intermixed. The result is 0123456789. ZClass does not inherit the IEnumerable interface. However, it adheres to the enumerator pattern by implementing the GetEnumerator method. This is sufficient for defining enumerable objects.
C# .NET Iterator Internals
Iterators are implemented as nested classes, which are created by the C# compiler. The nested class maintains the state of the current enumeration. It persists the enumeration state between yield statements. Iterators are created by the language compiler, not by the Common Language Runtime (CLR). Neither Microsoft intermediate language (MSIL) nor metadata has changed to especially accommodate iterators. The nested class for an enumerator is a normal class, which is created for a method that contains an iterator block. If three methods within a class have enumerator blocks, the compiler adds three nested classes to that class.
This is how the nested class is named:
-
<membername>uniqueid<T>
-
<membername>uniqueid
If the iterator method returns either the IEnumerator<T> or IEnumerable<T> interfaces, the name of the nested class has the <T> suffix.
In this code, ZClass has multiple iterator methods:
|
public class ZClass {
public IEnumerator GetEnumerator() { int[] array = new int[] { 1, 2, 3, 4 }; int count = 0; for (count = 0; count < 4; ++count) { yield return array[count]; } } public IEnumerator MethodA() { int[] array = new int[] { 1, 2, 3, 4 }; int count = 0; for (count = 0; count < 4; ++count) { yield return array[count]; } }
public IEnumerable<T> MethodB<T>() { T local = default(T); yield return local; }
} |
The compiler adds three nested classes, one for each enumerator method, to the ZClass type. The various nested classes represent the state machine for different enumerators.
Restrictions with Iterators (Blocks and Methods) in C# .NET
Iterator blocks contain the logic to enumerate a collection. This includes one or more yield statements. Methods, properties, and operator functions can be iterator blocks. Iterator blocks are not executed continuously and are sometimes suspended. The function is suspended between successive yield statements, which are controlled by the MoveNext method. As mentioned, the iterator block maintains the state machine of the enumerator between iterations.
There are restrictions on iterator blocks. For example, iterator blocks cannot have a return statement. Only yield return statements are allowed:
|
public IEnumerator<T> GetEnumerator() { foreach (T item in items) { yield return item; return; // not allowed } } |
There are several other restrictions on iterator blocks:
-
Iterator blocks can be contained only in method, operator, or property functions.
-
Iterator blocks cannot be in anonymous methods.
-
Iterator blocks cannot be contained in a try with a catch handler.
-
Iterator blocks cannot be placed in a finally block.
Functions with iterators or iterator methods also have certain restrictions:
-
Iterator methods must return a generic or nongeneric IEnumerable or IEnumerator interface.
-
Iterator methods cannot have ref parameters.
-
Iterator methods cannot have out parameters.
-
Iterator methods cannot be unsafe.
When an iterator block is exited, the Dispose method of the enumerator is called. Internally, the iterator creates the enumerator in a using block. This provides an opportunity to clean up for the enumerator when the enumeration is completed.
C# .NET Iterators : Yield Statement
The following code demonstrates one of the obvious benefits of the yield statement and an iterator block: brevity. The previous sample code of a generic enumerator required more than 50 lines of code. The following example implements a similar enumerator using the yield statement, which requires three lines of code:
|
public IEnumerator<T> GetEnumerator() { foreach (T item in items) { yield return item; } } |
In the previous code, the C# compiler implements the enumerator. You are still coding the IEnumerable interface. Optionally, the compiler can implement both the enumerable and enumerator object in the iterator. If the iterator block returns IEnumerable, the compiler responds by creating an enumerable and enumerator object. Iterator blocks are explained soon. This removes even having to implement the GetEnumerator method.
|
public IEnumerable<T> MethodA() { foreach (T item in items) { yield return item; } } |
For clients, iterators are similar to enumerators. Clients call GetEnumerator or other iterator methods to obtain an iterator object. You then use the IEnumerator or IEnumerator<T> interface to enumerate the collection. There is one big difference between iterators and enumerators: Iterators do not implement the Reset method. Calling the Reset method on an iterator causes an exception.
The pivotal statement of an iterator is yield. This is the syntax of the yield statement:
-
yield return expression;
-
yield break;
The yield return statements iterate the next element of a collection. The statement expression is assigned to the Current property. Enumerators start in the Before state. The initial MoveNext method calls the first yield statement. After the Current property is set, the enumerator is suspended. The next MoveNext calls the next yield. This pattern continues until the enumeration is finished. The iterator block is not called anew for each MoveNext. Between yield statements of the same iterator block, enumeration is suspended. The iterator is a state machine that maintains the state of the enumeration between calls to the MoveNext method.
The yield break statements finish an enumeration, which ultimately changes the enumerator state to after. The Dispose method is then called to clean up the enumerator. This is sample code of the yield break statement. The yield break statement stops the enumeration after the fourth element.
|
public IEnumerator<T> GetEnumerator() { int count = 0; foreach (T item in items) { ++count; yield return item; if (count == 4) { yield break; } } } |
C# .NET Generic Enumerator Example (Versioned Collection)
In an earlier post, a version collection model of enumeration was presented. Here is the versioned collection example redone with generic interfaces. The following code also completes some of the partial code presented earlier in this section. In Main, the collection is enumerated in a generic and nongeneric manner. In the second foreach loop, the simple collection is cast to the nongeneric IEnumerable interface. This instructs the foreach statement to call the nongeneric GetEnumerator method, which returns a nongeneric enumerator. The nongeneric enumerator is then used to iterate the simple collection.
Here is the sample code:
|
using System; using System.Collections; using System.Collections.Generic;
namespace Examples.Iterators { public class Starter { public static void Main() { SimpleCollection<int> simple = new SimpleCollection<int>(new int[] { 1, 2, 3, 4, 5, 6, 7 });
foreach (int number in simple) { Console.WriteLine(number); }
foreach (int number in (IEnumerable)simple) { Console.WriteLine(number); } } }
public class SimpleCollection<T> : IEnumerable<T> { public SimpleCollection(T[] array) { items = array; version = 1; }
public T this[int index] { get { return items[index]; } set { ++version; items[index] = value; } }
public IEnumerator<T> GetEnumerator() { Console.WriteLine(“IEnumerator<T> GetEnumerator()”); return new Enumerator<T>(this); }
IEnumerator IEnumerable.GetEnumerator() { Console.WriteLine(“IEnumerator GetEnumerator()”); return new Enumerator<T>(this); }
private class Enumerator<__T> : IEnumerator<__T> { public Enumerator(SimpleCollection<__T> obj) { oThis = obj; cursor = -1; version = oThis.version; }
public __T Current { get { if (oThis.version != version) { throw new InvalidOperationException(“Collection was modified”); }
if (cursor > (oThis.items.Length – 1)) { throw new InvalidOperationException(“Enumeration already finished”); } if (cursor == -1) { throw new InvalidOperationException(“Enumeration not started”); } return oThis.items[cursor]; } }
public void Dispose() { cursor = oThis.items.Length + 1; }
public bool MoveNext() { ++cursor; if (cursor > (oThis.items.Length – 1)) { return false; } return true; }
public void Reset() { cursor = -1; }
object IEnumerator.Current { get { return Current; } }
private int version; private int cursor; private SimpleCollection<__T> oThis; }
private T[] items = null; private int version; } } |
Generic Enumerators in C# .NET
Nongeneric enumerable objects and enumerators are oblique. There is a lack of type specialty, which leads to performance problems and other issues. You can implement enumerable objects and enumerators using generic interfaces, which avoid some of the problems mentioned earlier. Implement the IEnumerable<T> interface for generic enumerable objects. For generic enumerator objects, implement the IEnumerator<T> interface. Both IEnumerable<T> and IEnumerator<T> are generic interfaces found in the .NET FCL in the System.Collections .Generic namespace. IEnumerable<T> and IEnumerator<T> inherit their nongeneric counterparts IEnumerable and IEnumerator, respectively. This means that generic enumerable objects and enumerators are available generically or nongenerically.
IEnumerable<T> Interface
Generic enumerable objects implement the IEnumerable<T> interface. This is the IEnumerable<T> interface:
|
public interface IEnumerable<T> : IEnumerable { IEnumerator<T> GetEnumerator(); } |
As shown, IEnumerable<T> inherits IEnumerable, which is the nongeneric version of the same interface. This includes a nongeneric version of the GetEnumerator method. Enumerators inheriting IEnumerable<T> must therefore implement a generic and nongeneric GetEnumerator method. The two GetEnumerator methods differ in return type only. As you know, the return type alone is insufficient for overloading a method. To prevent ambiguousness, one of the GetEnumerator methods must have explicit interface member implementation.
This is sample code of the GetEnumerator methods for a generic enumerable object. (The nongeneric version of GetEnumerator is implemented explicitly.)
|
public IEnumerator<T> GetEnumerator() { return new Enumerator<T>(this); }
IEnumerator IEnumerable.GetEnumerator() { return new Enumerator<T>(this); } |
The generic version of GetEnumerator naturally returns a generic enumerator, which implements the IEnumerator<T> interface.
IEnumerator<T> Interface
Generic enumerators implement the IEnumerator<T> interface, shown here:
|
public interface IEnumerator<T> : IDisposable, IEnumerator { T Current { get; } } |
Current is a read-only property and is the only direct member of the IEnumerator<T> generic interface. The remaining members are inherited from the IDisposable and IEnumerator interfaces. The IDisposable interface defines generic enumerators as disposable. This requires implementing the IDisposable.Dispose method. The IEnumerator interface adds the nongeneric enumerator interface. The MoveNext and Reset methods do not require a generic implementation and are therefore not defined in the generic portion of the interface. A second Current property, a nongeneric version, is inherited from the IEnumerable interface. Therefore, IEnumerator has overloaded Current properties, where both should be implemented in the enumerator.
This is sample code of a generic and nongeneric implementation of the Current property. The nongeneric Current property delegates to the generic version.
|
public __T Current { get { if (oThis.version != version) { throw new InvalidOperationException(“Collection was modified”); }
if (cursor > (oThis.items.Length – 1)) { throw new InvalidOperationException(“Enumeration already finished”); } if (cursor == -1) { throw new InvalidOperationException(“Enumeration not started”); } return oThis.items[cursor]; } }
object IEnumerator.Current { get { return Current; } } |
The Dispose method supports deterministic garbage collection. This method is called explicitly to clean up for an object. In this circumstance, the method is called to clean up resources assigned to an enumerator. Dispose methods of enumerators are most frequently called by iterators, which is the next topic of this chapter. In the Dispose method, set the state of the enumeration to After and perform any necessary cleanup. Some enumerators track the state using a flag (an enumeration type), where the flag is updated in the Dispose method and elsewhere in the enumerator. The code presented earlier does not employ a state flag. If the cursor is beyond the collection, the After state is assumed. Conversely, a cursor of -1 indicates the Before state. Based on these assumptions, this is one implementation of a Dispose method for an enumerator:
|
public void Dispose() { cursor = oThis.items.Length + 1; } |
C# .NET Enumeration : Another Example (Versioned Collection)
The following code contains an implementation for a versioned collection. A private field called version has been added to the collection class. An indexer has been added to allow clients to modify the collection. The version is incremented whenever the collection is modified. A version number has also been added to the enumerator, which is the nested class. In the constructor, the version is initialized to the version of the outer collection. When the Current property is accessed, the version number inside the enumerator is compared to that of the collection. If unequal, the collection has been modified since the enumerator was created and an exception is raised. This is the implementation model of the collections in the .NET Framework class library (FCL), such as the ArrayList, Stack, Queue, and other collection classes.
The Main method is changed to test the versioning. The collection is modified in the method after the enumerator has been obtained. After the modification, the Current property is used, which causes the expected exception:
|
using System; using System.Collections;
namespace Examples.Iterators { public class Starter { public static void Main() { SimpleCollection simple = new SimpleCollection(new object[] { 1, 2, 3, 4, 5, 6, 7 });
IEnumerator enumerator = simple.GetEnumerator(); enumerator.MoveNext(); Console.WriteLine(enumerator.Current); enumerator.MoveNext(); simple[4] = 10; Console.WriteLine(enumerator.Current); // Exception raised enumerator.MoveNext();
Console.ReadKey(); } }
public class SimpleCollection : IEnumerable { public SimpleCollection(object[] array) { items = array; version = 1; }
public object this[int index] { get { return items[index]; } set { ++version; items[index] = value; } }
public IEnumerator GetEnumerator() { return new Enumerator(this); }
private class Enumerator : IEnumerator { public Enumerator(SimpleCollection obj) { oThis = obj; cursor = -1; version = oThis.version; }
public bool MoveNext() { ++cursor; if (cursor > (oThis.items.Length – 1)) { return false; } return true; }
public void Reset() { cursor = -1; }
public object Current { get { if (oThis.version != version) {
throw new InvalidOperationException(“Collection was modified”); }
if (cursor > (oThis.items.Length – 1)) { throw new InvalidOperationException(“Enumeration already finished”); } if (cursor == -1) { throw new InvalidOperationException(“Enumeration not started”); } return oThis.items[cursor]; } }
private int version; private int cursor; private SimpleCollection oThis; }
private object[] items = null; private int version; } } |
C# .NET Enumeration : An Example (Static Collection)
Here is the SimpleCollection class rewritten for a static collection. The underlying collection is read-only, which makes it static. When the enumerator is created, the this reference of the enumerable object is passed to the constructor. Future and unique enumerators share this reference to access the same collection—maybe simultaneously. The state machine encapsulates the back reference and a cursor. The back reference is the this reference to the outer object. With the back reference, the enumerator gains access to members of the outer class, including the collection, which allows the enumerator to iterate the collection directly.
|
public class SimpleCollection : IEnumerable { public SimpleCollection(object[] array) { items = array; }
public IEnumerator GetEnumerator() { return new Enumerator(this); }
private class Enumerator : IEnumerator { public Enumerator(SimpleCollection obj) { oThis = obj; cursor = -1; }
public bool MoveNext() { ++cursor; if (cursor > (oThis.items.Length – 1)) { return false; } return true; }
public void Reset() { cursor = -1; }
public object Current { get { if (cursor > (oThis.items.Length – 1)) { throw new InvalidOperationException(“Enumeration already finished”); } if (cursor == -1) { throw new InvalidOperationException(“Enumeration not started”); } return oThis.items[cursor]; } }
private int cursor; private SimpleCollection oThis; }
private object[] items = null; } |
Collections that have inconstant changes are not ideal for either of the enumerator models presented: contentious or static. Copying the collection per each enumerator is costly when changes are infrequent. The static model is inappropriate because the collection may indeed change, albeit not regularly. A versioned collection is the solution and combines traits of the contentious and static models for implementing enumerators.
C# .NET Enumerators : A Simple Example
Sample code for an enumerator class and client are provided as follows. This is also the complete listing for some of the partial code presented in the previous post. The SimpleCollection is a thin wrapper for a basic array. Actually, it is somewhat redundant because arrays are already fully functional collections, including exposing an enumerator. However, this simple example is ideal for demonstrating the enumerator pattern.
The enumerator pattern recommends isolation of the underlying collection. In this code, the enumerator is created as a nested class, in which a snapshot of the collection is made in the constructor. The isolated collection is created from a copy of the array. In addition, the Current property is read-only, which prevents changes to the collection data.
In Main, an instance of the SimpleCollection is created. It is initialized with an integer array. The collection is then iterated using the IEnumerator interface.
|
using System; using System.Collections;
namespace Examples.Iterators { public class Program { public static void Main() { SimpleCollection simple = new SimpleCollection(new object[] { 1, 2, 3, 4, 5, 6, 7 });
IEnumerator enumerator = simple.GetEnumerator(); while (enumerator.MoveNext()) { Console.WriteLine(enumerator.Current); }
Console.ReadKey(); } }
public class SimpleCollection : IEnumerable { public SimpleCollection(object[] array) { items = array; }
public IEnumerator GetEnumerator() { return new Enumerator(items); }
private class Enumerator : IEnumerator {
public Enumerator(object[] items) { elements = new object[items.Length]; Array.Copy(items, elements, items.Length); cursor = -1; }
public bool MoveNext() { ++cursor; if (cursor > (elements.Length – 1)) { return false; } return true; }
public void Reset() { cursor = -1; }
public object Current { get { if (cursor > (elements.Length – 1)) { throw new InvalidOperationException(“Enumeration already finished”); } if (cursor == -1) { throw new InvalidOperationException(“Enumeration not started”); } return elements[cursor]; } }
private int cursor; private object[] elements = null; }
private object[] items = null; } } |
As mentioned, the SimpleCollection class makes a copy of the collection in the enumerator. This isolates the collection from contamination caused by modifications. This is the recommended approach for volatile collections. SimpleCollection is volatile because the elements of the underlying collection can be modified. If the collection is static, a copy may not be necessary. Isolation protects a collection against changes. If the collection is static, this is not an issue, and isolation is not required.
Enumerators in C# .NET
Enumerators are part of the enumeration pattern and normally implemented as a nested class within the collection type. The primary benefit of nested classes is access to the private members of the outer class. This access allows you to avoid breaking the rules of encapsulation to allow the enumerator class to access the data store of the collection. The data store is undoubtedly a private member of the collection class.
Enumerators implement the IEnumerator interface, which has three members. This is the IEnumerator interface:
|
public interface IEnumerator { object Current { get; } bool MoveNext(); void Reset(); } |
The Current property returns the current element of the collection. MoveNext moves the Current property to the next element. If the iteration has completed, MoveNext returns false. Otherwise, MoveNext returns true. Notice that there is not a MovePrevious method; enumeration is forward only. The Reset method returns the enumeration to the beginning of the collection.
The enumerator is the state machine representing the enumeration. Part of the state machine is the cursor, which is the collection index or locator. The cursor is not necessarily an integral value, but normally it is. For a link list, the cursor may be a node, which is an object. When the enumerator is created, the cursor initially points before the first element of the collection. Do not access the Current property while the cursor is in this initial state. Call MoveNext first, which positions the cursor at the first element of the collection.
The following is a typical constructor of an enumerator. In this example, the constructor makes a snapshot of the collection. For reasons of simplicity, the collection is a basic array. The cursor is then set to –1, which is before the first element of the collection.
|
public Enumerator(object[] items) { elements = new object[items.Length]; Array.Copy(items, elements, items.Length); cursor=-1; } |
The MoveNext method increments the value of the cursor. This action moves the Current property to the next element of the collection. If the list has been fully iterated, MoveNext returns false. Collections are not circular, where MoveNext might cycle through a collection. Circular collections are not within the enumerator pattern. The Current property is not valid after the collection has been fully enumerated. For this reason, do not use the Current property after MoveNext returns false.
Here is one possible implementation of the MoveNext method:
|
public bool MoveNext() { ++cursor; if(cursor>(elements.Length-1)) { return false; } return true; } |
The Reset method resets the enumeration. The cursor is updated to point to before the collection again. Resetting the cursor also automatically resets the Current property.
Here is a Reset method:
|
public void Reset() { cursor=-1; } |
With the cursor as a reference to the current item, the Current property provides access to the current element of the collection. The Current property must be read-only. Implement the get method of the property but not the set method. The implementation should check for fence-post errors. If the index is before or after the collection, the appropriate exception should be thrown.
Here is an implementation of the Current property:
|
public object Current { get { if (cursor > (elements.Length – 1)) { throw new InvalidOperationException(“Enumeration already finished”); } if (cursor == -1) { throw new InvalidOperationException(“Enumeration not started”); } return elements[cursor]; } } |
Enumerator states Enumerators can be in one of four possible states. The table lists the enumerator states.
|
State |
Description |
|
Before |
This is the state of the enumerator before enumeration has started or after it has been reset. The first call to MoveNext changes the state from Before to Running. |
|
Running |
This is the state when MoveNext is calculating the next element (Current) of the iteration. When MoveNext returns true, the next element has been enumerated, and the state changes to Suspended. If MoveNext returns false, the state changes to After. At that time, the Current property is no longer available. |
|
Suspended |
The state of the enumerator between enumerations. Calling MoveNext changes the state from Suspended to Running while calculating the next Current property. |
|
After |
This is the state after enumeration has completed. Reset returns the enumeration to Before, and enumeration can be restarted. |
-
Archives
- February 2009 (1)
- November 2008 (6)
- October 2008 (4)
- September 2008 (13)
- August 2008 (11)
- July 2008 (29)
- June 2008 (19)
- May 2008 (8)
-
Categories
-
RSS
Entries RSS
Comments RSS

