Understanding Enumerators and Iterators in C#

Enumerators and Iterators

1. Enumerators and Enumerable Types

Using the foreach Statement

Arrays can provide an enumerator object on demand. An enumerator returns array elements one at a time when requested. The enumerator knows the order of items and tracks its position in the sequence. For types with enumerators, there must be a way to retrieve them.

  • The method to get an object's enumerator is calling the object's GetEnumerator method. Types implementing GetEnumerator are called enumerable types.
  • Arrays are enumerable types.

The foreach structure is designed to work with enumerable types. When given an enumerable object like an array, it performs the following actions:

  • Gets the object's enumerator by calling GetEnumerator
  • Requests each item from the enumerator and assigns it to the iteration variable. The code can read this variable but cannot modify it.
foreach(Type VarName in enumerableObject)
{
    // ...
}

2. IEnumerator Interface

Enumerator classes implementing the IEnumerator interface contain three function members:

  • Current - Returns the item at the current position in the sequence. It's a read-only property that returns an object reference, allowing it to return any type of object.
  • MoveNext - Advances the enumerator position to the next item in the collection. Returns a boolean indicating whether the new position is valid or if the end of the sequence has been reached. Returns true for valid positions, false when the end is reached. The enumerator's initial position is before the first item, so MoveNext must be called before using Current for the first time.
  • Reset - Resets to the original state.

How an enumerator tracks the current item depends entirely on the implementation. It can use object references, index values, or other methods. For built-in one-dimensional arrays, item indices are used.

3. IEnumerable Interface

Enumerable classes implement the IEnumerable interface, which has only one member: the GetEnumerator method that returns the object's enumerator.

using System.Collections;
class MyClass : IEnumerable
{
    public IEnumerator GetEnumerator() { }
}

Example Using IEnumerable and IEnumerator

class ColorEnumerator : IEnumerator
{
    string[] colors;
    int position = -1;
    
    public ColorEnumerator(string[] theColors)
    {
        colors = new string[theColors.Length];
        for(int i = 0; i < theColors.Length; i++)
        {
            colors[i] = theColors[i];
        }
    }
    
    public object Current
    {
        get
        {
            if(position < 0 || position >= colors.Length)
            {
                throw new InvalidOperationException();
            }
            return colors[position];
        }
    }
    
    public bool MoveNext()
    {
        if(position < colors.Length - 1)
        {
            position++;
            return true;
        }
        else
        {
            return false;
        }
    }
    
    public void Reset()
    {
        position = -1;
    }
}
class Spectrum : IEnumerable
{
    string[] Colors = {"blue", "yellow", "green", "black"};
    
    public IEnumerator GetEnumerator()
    {
        return new ColorEnumerator(Colors);
    }
}
static void Main(string[] args)
{
    Spectrum spectrum = new Spectrum();
    foreach(string color in spectrum)
    {
        Console.WriteLine(color);
    }
}

4. Generic Enumeration Interfaces

For non-generic interface forms:

  • The IEnumerable interface's GetEnumerator method returns an instance of an enumerator class implementing IEnumerator.
  • Classes implementing IEnumerator implement the Current property, which returns an object reference that must be cast to the actual type.

For generic interfaces:

  • The IEnumerable<T> interface's GetEnumerator method returns an instance of an enumerator implementing IEnumerator<T>.
  • The Current property returns an instance of the actual type, not an object refernece.
  • These are covariant interfaces, so their actual declarations are IEnumerable<out T> and IEnumerator<out T>.

5. Iterators

Iterators are a simpler way provided by the C# compiler to create enumerators and enumerable types. The yield return statement specifies the next item in the enumeration.

Iterator Blocks

Iterator blocks are code blocks containing one or more yield statements. They can be:

  • Method bodies
  • Accessor bodies
  • Operator bodies

Iterator blocks differ from other blocks:

  • Other blocks contain imperative statements executed sequentially.
  • Iterator blocks are declarative, describing the behavior of the enumerator the compiler should create. The code describes how to enumerate elements.

Iterator blocks have two special statements:

  • yield return specifies the next item to return in the sequence.
  • yield break specifies that there are no more items in the sequence.

After the compilre gets the description of how to enumerate items, it builds the enumerator class with all necessary method and property implementations. The generated class is nested within the class declaring the iterator.

// Producing an enumerator
public IEnumerator<string> IteratorMethod()
{
    yield return...;
}
// Producing an enumerable type
public IEnumerable<string> IteratorMethod()
{
    yield return...;
}

Using Iterators to Create Enumerators

BlackAndWhite is an iterator block that can produce a method returning an enumerator for the MyClass class. MyClass implements GetEnumerator() and calls the iterator block to return the enumerator. MyClass is an enumerable type, so it can be used directly with foreach in the main method.

class MyClass
{
    // Implements GetEnumerator(), making it enumerable
    public IEnumerator<string> GetEnumerator()
    {
        return BlackAndWhite();
    }
    
    // Uses iterator to produce enumerator
    public IEnumerator<string> BlackAndWhite()
    {
        yield return "black";
        yield return "gray";
        yield return "white";
    }
}
static void Main(string[] args)
{
    MyClass mc = new MyClass();
    foreach (string color in mc)
    {
        Console.WriteLine(color);
    }
}

Using Iterators to Create Enumerable Types

The BlackAndWhite iterator method returns an IEnumerable<string> enumerable object. Therefore, MyClass first calls BlackAndWhite() to get its enumerable object, then calls the object's GetEnumerator() to get the result, implementing GetEnumerator().

class MyClass
{
    public IEnumerator<string> GetEnumerator()
    {
        // Get enumerable type
        IEnumerable<string> myEnumerable = BlackAndWhite();
        // Get enumerator
        return myEnumerable.GetEnumerator();
    }
    
    // Uses iterator to produce enumerable type
    public IEnumerable<string> BlackAndWhite()
    {
        yield return "black";
        yield return "gray";
        yield return "white";
    }
}
static void Main(string[] args)
{
    MyClass mc = new MyClass();
    // Using class object
    foreach (string color in mc)
    {
        Console.WriteLine($"{color}");
    }
    // Using class enumerator method
    foreach (string color in mc.BlackAndWhite())
    {
        Console.WriteLine($"{color}");
    }
}

6. Common Iterator Patterns

Enumerator Iterator Pattern

The class must implement GetEnumerator() to be enumerable, returning the enumerator produced by the iterator.

class MyClass
{
    public IEnumerator<XXX> GetEnumerator()
    {
        return IteratorMethod();
    }
    public IEnumerator<XXX> IteratorMethod()
    {
        yield return xxx;
    }
}
static void Main(string[] args)
{
    MyClass mc = new MyClass();
    // Using class object
    foreach (XXX xxx in mc)
    {
        Console.WriteLine($"{xxx}");
    }
}

Enumerable Type Iterator Pattern

A class can implement GetEnumerator() to be enumerable, or not implement it to be non-enumerable.

class MyClass
{
    public IEnumerable<XXX> GetEnumerator()
    {
        return IteratorMethod().GetEnumerator();
    }
    public IEnumerable<XXX> IteratorMethod()
    {
        yield return xxx;
    }
}
static void Main(string[] args)
{
    MyClass mc = new MyClass();
    // Using class object
    foreach (XXX xxx in mc)
    {
        Console.WriteLine($"{xxx}");
    }
    // Using class enumerator method
    foreach (XXX xxx in mc.IteratorMethod())
    {
        Console.WriteLine($"{xxx}");
    }
}

7. Producing Multiple Enumerable Types

The following class has two iterator methods returning enumerable types, from UV to IR and IR to UV. Although there are two methods returning enumerable types, the class itself is not enumerable as it doesn't implement GetEnumerator().

class Spectrum
{
    string[] colors = {"blue", "green", "yellow"};
    
    // Producing enumerable type
    public IEnumerable<string> UVtoIR()
    {
        for(int i = 0; i < colors.Length; i++)
        {
            yield return colors[i];
        }
    }
    
    // Producing enumerable type
    public IEnumerable<string> IRtoUV()
    {
        for(int i = colors.Length - 1 ; i >= 0 ; i--)
        {
            yield return colors[i];
        }
    }
}
static void Main(string[] args)
{
    Spectrum sp = new Spectrum();
    // Using class enumerator method
    foreach (string color in sp.UVtoIR())
    {
        Console.WriteLine($"{color}");
    }
    // Using class enumerator method
    foreach (string color in sp.IRtoUV())
    {
        Console.WriteLine($"{color}");
    }
}

8. Using Iterators as Properties

This example shows how iterators can be implemented as properties rather than methods.

class Spectrum
{
    // Returns enumerator based on this property
    bool _listFromUVtoIR;
    
    string[] colors = {"blue", "green", "yellow"};
    
    // Constructor
    public Spectrum(bool listFromUVtoIR)
    {
        _listFromUVtoIR = listFromUVtoIR;
    }
    
    // Producing enumerator
    public IEnumerator<string> GetEnumerator()
    {
        return _listFromUVtoIR ? UVtoIR : IRtoUV;
    }
    
    public IEnumerator<string> UVtoIR()
    {
        for(int i = 0; i < colors.Length; i++)
        {
            yield return colors[i];
        }
    }
    
    public IEnumerator<string> IRtoUV()
    {
        for(int i = colors.Length - 1 ; i >= 0 ; i--)
        {
            yield return colors[i];
        }
    }
}
static void Main(string[] args)
{
    Spectrum spUV = new Spectrum(true);
    Spectrum spIR = new Spectrum(false);
    // Using class enumerator
    foreach (string color in spUV)
    {
        Console.WriteLine($"{color}");
    }
    // Using class enumerator
    foreach (string color in spUV)
    {
        Console.WriteLine($"{color}");
    }
}

9. The Essence of Iterators

Iterators require the using System.Collections.Generic; namespace. In the compiler-generated enumerator, Reset is not supported (it's required by the interface), so implementing it would throw an exception when called.

Behind the scenes, the compiler-generated enumerator class is a state machine with four states:

  • Before - Initial state before the first MoveNext call
  • Running - State after calling MoveNext
    • The enumerator detects and sets the position of the next item, exiting the state when encountering yield return, yield break, or at the end of the iterator body.
  • Suspended - State waiting for the next MoveNext call
  • After - State when no more items can be enumerated

If MoveNext is called when the state machine is in Before or Suspended state, it transitions to Running. In the Running state, it detects the next item in the collection and sets the position. If there are more items, it transitions to Suspended; otherwise, it transitions to and remains in the After state.

Tags: C# IEnumerable IEnumerator yield enumerators

Posted on Fri, 31 Jul 2026 16:29:43 +0000 by lauthiamkok