Type Behavioral
Purpose Provide a way to access the elements in an aggregate object sequentially without exposing the underlying representation.

Description

The iterator pattern is an abstraction over a collection. It provides a way to access the elements of an aggregate object (like a collection) sequentially without exposing its underlying representation.


Overview

Iterator
Aggregate The aggregate defines an interface for creating an iterator, for example IEnumerable<string>
ConcreteAggregate The concrete object that implements the Aggregate interface and provides an iterator over its elements.
Iterator The iterator defines an interface for accessing and traversing elements, for example the IEnumerator<string> interface.
ConcreteIterator This is the actual implementation of the iterator.
Client The entity that uses the Iterator to access and traverse the elements of the collection or aggregate object, without needing to know about the underlying structure of the collection.

Implementation

IEnumerable and IEnumerator are central to iteration in .NET. In early C#, custom iterators often required manually implementing IEnumerator, whereas today iterator implementations can frequently be expressed more simply using iterator methods with yield. LINQ also provides a rich set of operations over IEnumerable<T> sequences.

public readonly record struct 
    Customer(string Name, string Contact, bool IsPreferred = false);

public static class IteratorDemo
{
    private static readonly List<Customer> Customers =
    [
        new("John", "0123456789", IsPreferred: true),
        new("Jane", "9876543210"),
        new("Jack", "1234567890"),
        new("Jill", "6789012345", IsPreferred: true),
    ];
    
    public static void Execute()
    {
        Console.WriteLine("Preferred Customers:");
                        
        foreach (var name in PreferredCustomerNames)
        {
            Console.WriteLine(name);
        }
    }    

    private static IEnumerable<string> PreferredCustomerNames
        => Customers.Where(c => c.IsPreferred).Select(c => c.Name);
}

Notes

Referring to the code above:

  • Aggregate: IEnumerable<string> represents the abstraction through which the sequence can be enumerated.
  • ConcreteAggregate: The LINQ expression returned by PreferredCustomerNames produces an IEnumerable<string> sequence backed by the underlying Customers collection and LINQ operators.
  • Iterator: IEnumerator<string> represents the interface used to traverse the sequence.
  • ConcreteIterator: The concrete enumerator is supplied by the implementation of the LINQ-generated enumerable when GetEnumerator() is called. Client code normally does not need to know its concrete type.

In C#, IEnumerable is pervasive:

private static IEnumerable<string> PreferredCustomerNames()
{
    foreach (var customer in Customers)
    {
        if (customer.IsPreferred)
            yield return customer.Name;
    }
}

These interfaces and their implementations abstract away much of the implementation detail of the Iterator pattern.

In C# 1.0, enumerators were a bit more work:

public class MyConcreteIterator : IEnumerator
{
    private object[] items;
    private int position = -1;

    public MyConcreteIterator(object[] items)
    {
        this.items = items;
    }

    public object Current => items[position];

    public bool MoveNext()
    {
        if (position < items.Length - 1)
        {
            position++;
            return true;
        }
        return false;
    }

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

The ConcreteIterator, in the context of the Iterator pattern, is the one responsible for the actual traversal of the collection. The key tasks of a ConcreteIterator include:

  1. Keeping track of the current position within the aggregate (collection)
  2. Determining the sequence in which elements are traversed
  3. Providing a way to access the current element
  4. Checking if there are more elements to traverse
  5. Potentially handling the functionality to reset the iterator back to its initial position

Summary

For more information, please see here.