C# Iterator

Summary: in this tutorial, you will learn about the C# iterator pattern and how to use it to define an interface that iterates the elements in a collection.

Introduction to the C# Iterator design pattern

The Iterator is a behavioral design pattern that allows you to define an interface for iterating elements of a collection without exposing its underlying implementation. The Iterator pattern does this by separating the iteration logic from the collection object.

The following UML diagram illustrates the C# Iterator pattern:

C# Iterator

The Iterator pattern has the following participants:

  • Iterator is an interface that defines a set of methods for iterating elements in a collection. Typically, the Iterator interface consists of the Current property, the MoveNext() method, and Reset() method. The Current property returns the current element in the collection. The MoveNext() method returns true if the next element exists and advances to the next element or returns false if no more elements to iterate. The Reset() is an optional method that resets the iterator to the initial state.
  • ConcreteIterator is a class that provides a concrete implementation of the Iterator interface that iterates elements in the collection.
  • Aggregate is an interface that has the createIterator() method for creating an Iterator object. Typically, you define a collection class that implements the Aggregate interface, and use the Iterator object to iterate the elements in the collection object.
  • ConcreteAggregate is a concrete implementation of the Aggregate interface that creates a ConcreteIterator object for iterating the collection.

C# Iterator pattern example

Suppose you have a collection of calendar months from January to December, and you want to iterate through it in the sequence of Jan, Feb, Mar, … Dec.

Additionally, if the fiscal month starts in April instead of January, you need to provide a way to iterate the months in the following sequence: Apr, May, … Dec, Jan, Feb, Mar.

To achieve this kind of iteration, you can use the Iterator pattern. Here are the steps:

First, define an Iterator interface that has methods for iterating months:

public interface IMonthIterator
{
    string Current { get; }
    bool MoveNext();
    void Reset();
}Code language: C# (cs)

Second, create an IAggregate interface that defines a method for creating an IMonthIterator object:

public interface IAggregate
{
    IMonthIterator CreateIterator();
}Code language: C# (cs)

Third, define the Months collection that implements the IAggregate interface:

public class Months: IAggregate
{
    private readonly string[] _months = {
        "Jan","Feb","Mar",
        "Apr","May","Jun",
        "Jul","Aug","Sep",
        "Oct","Nov","Dec",
    };

    public int FiscalMonthStart { get; set; } = 1;

    public IMonthIterator CreateIterator()
    {
        return new MonthIterator(_months, FiscalMonthStart);
    }
}Code language: C# (cs)

In the Months class:

  • The _months private field holds an array of months from Jan to Dec.
  • The FiscalMonthStart property defaults to 1, indicating that the fiscal month starts in January by default.
  • The CreateIterator() method returns a new MonthIterator which is the concrete iterator of the IMonthIterator interface. The constructor of the MonthIterator accepts two arguments, the _months array, and the FiscalMonthStart.

Fourth, define the MonthIterator class that implements the IMonthIterator interface:

public class MonthIterator: IMonthIterator
{
    private int _index;
    private int _count = 0;
    private readonly string[] _months;
    private readonly int _fiscalMonthStart;

    public MonthIterator(string[] months, int fiscalMonthStart)
    {
        if (months.Length != 12)
        {
            throw new ArgumentException("The number of months is not 12");
        }

        if (fiscalMonthStart < 0 || fiscalMonthStart > 12)
        {
            throw new ArgumentOutOfRangeException(nameof(fiscalMonthStart));
        }

        _months = months;
        _fiscalMonthStart = fiscalMonthStart;

        _index = _fiscalMonthStart - 2;
    }

    public string Current
    {
        get
        {
            if (_index < 0 || _index > _months.Length)
            {
                throw new IndexOutOfRangeException();
            }
            _count++;
            return _months[_index];
        }
    }

    public bool MoveNext()
    {
        if (_count >= 12)
        {
            return false;
        }

        _index++;
        if (_index == _months.Length)
        {
            _index = 0;
        }

        return true;
    }

    public void Reset()
    {
        _count = 0;
        _index = _fiscalMonthStart - 2;
    }
}Code language: C# (cs)

In the MonthIterator class:

  • The _index field holds the current position of the month in the _months array.
  • The _months array stores an array of months from Jan to Dec.
  • The _count field holds the number of months that have been iterated. Its values are from 1 to 12. If you iterate all the months, the _count will be 12, and no more next element in the _months to return.
  • The _fiscalMonthStart stores the month number that the fiscal month will start e.g. _fiscalMonthStart is 1, which means the fiscal month will start in January.
  • The constructor validates the _months array to make sure that it has 12 elements and also ensures that the value of fiscalMonthStart is from 1 to 12. It assigns _months and _fiscalMonthStart fields and initializes the _index field.
  • The Current getter throws an IndexOutOfRangeException error if the _index is not in the valid range from 1 to 12, increases the _count, and returns the current element in the _months array based on the _index.
  • The MoveNext() method returns false if the _count is greater than 12. Otherwise, it increases the _index by one and returns true. If the _index is the same as the length of the _month array, reset it to zero.
  • The Reset() method resets the _count to zero and _index to _fiscalMonthStart - 2.

Finally, create a program that uses the iterator to iterate over months with the fiscal month starting in Jan and April:

public class Program
{
    public static void Main(string[] args)
    {
        var months = new Months();

        // fiscal month start in Jan by default
        Console.WriteLine("Fiscal month start in January:");
        var iterator = months.CreateIterator();
        while (iterator.MoveNext())
        {
            Console.Write($"{iterator.Current} ");
        }

        Console.WriteLine();

        // fiscal month start in April
        Console.WriteLine("Fiscal month start in April:");
        months.FiscalMonthStart = 4;

        iterator = months.CreateIterator();
        while (iterator.MoveNext())
        {
            Console.Write($"{iterator.Current} ");
        }
    }
}Code language: C# (cs)

Output:

Fiscal month start in January:
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
Fiscal month start in April:
Apr May Jun Jul Aug Sep Oct Nov Dec Jan Feb MarCode language: plaintext (plaintext)

Put it all together:

namespace IteratorPattern;

public interface IMonthIterator
{
    string Current {  get; }
    bool MoveNext();
    void Reset();
}

public interface IAggregate
{
    IMonthIterator CreateIterator();
}

public class Months : IAggregate
{
    private readonly string[] _months = {
        "Jan","Feb","Mar",
        "Apr","May","Jun",
        "Jul","Aug","Sep",
        "Oct","Nov","Dec",
    };

    public int FiscalMonthStart { get; set; } = 1;

    public IMonthIterator CreateIterator()
    {
        return new MonthIterator(_months, FiscalMonthStart);
    }
}

public class MonthIterator : IMonthIterator
{
    private int _index;
    private int _count = 0;
    private readonly string[] _months;
    private readonly int _fiscalMonthStart;

    public MonthIterator(string[] months, int fiscalMonthStart)
    {
        if (months.Length != 12)
        {
            throw new ArgumentException("The number of months is not 12");
        }

        if (fiscalMonthStart < 0 || fiscalMonthStart > 12)
        {
            throw new ArgumentOutOfRangeException(nameof(fiscalMonthStart));
        }

        _months = months;
        _fiscalMonthStart = fiscalMonthStart;

        _index = _fiscalMonthStart - 2;
    }

    public string Current
    {
        get
        {
            if (_index < 0 || _index > _months.Length)
            {
                throw new IndexOutOfRangeException();
            }
            _count++;
            return _months[_index];
        }
    }

    public bool MoveNext()
    {
        if (_count >= 12)
        {
            return false;
        }

        _index++;
        if (_index == _months.Length)
        {
            _index = 0;
        }

        return true;
    }

    public void Reset()
    {
        _count = 0;
        _index = _fiscalMonthStart - 2;
    }
}

public class Program
{
    public static void Run(string[] args)
    {
        var months = new Months();

        // fiscal month start in Jan by default
        Console.WriteLine("Fiscal month start in January:");
        var iterator = months.CreateIterator();
        while (iterator.MoveNext())
        {
            Console.Write($"{iterator.Current} ");
        }

        Console.WriteLine();

        // fiscal month start in April
        Console.WriteLine("Fiscal month start in April:");
        months.FiscalMonthStart = 4;

        iterator = months.CreateIterator();
        while (iterator.MoveNext())
        {
            Console.Write($"{iterator.Current} ");
        }
    }
}Code language: C# (cs)

Implementing Iterator pattern in C# using IEnumerable and IEnumerator interfaces

C# supports the Iterator design pattern out of the box via the IEnumerator and IEnumerable interfaces. For the modern C# code, you should use the IEnumerator<T> and IEnumerable<T> instead.

Also, C# allows you to iterate over the elements of a collection that implements the IEnumerable<T> interface using the foreach loop.

In C#, an iterator is also called an enumerator. The IEnumerable interface has the GetEnumerator() method that returns an enumerator for iterating over elements of a collection. It is equivalent to the Aggregate interface.

The IEnumerator interface has the Current property that returns the current element in the collection, the MoveNext() advances the enumerator to the next element of the collection, and the Reset() method sets the enumerator to its initial position. The IEnumerator is equivalent to the Iterator interface.

The following example shows how to use the IEnumable and IEnumerator interfaces to implement the Iterator pattern.

First, define the Months class that implements the IEnumerable interface. The GetEnumerator() method should return an instance of the IEnumerator interface. In this case, it returns an instance of the MonthIterator class:

public class Months : IEnumerable
{
    private readonly string[] _months = {
        "Jan","Feb", "Mar",
        "Apr","May", "Jun",
        "Jul","Aug", "Sep",
        "Oct","Nov", "Dec",
    };

    public int FiscalMonthStart { get; set; } = 1;

    public IEnumerator GetEnumerator()
    {
        return new MonthIterator(_months, FiscalMonthStart);
    }
}Code language: C# (cs)

Second, define the MonthIterator class that implements the IEnumerator interface:

public class MonthIterator : IEnumerator
{
    private int _index;
    private int _count = 0;
    private readonly int _fiscalMonthStart = 0;
    private readonly string[] _months;

    public MonthIterator(string[] months, int fiscalMonthStart)
    {
        if (months.Length != 12)
        {
            throw new ArgumentException("The number of months is not 12");
        }

        if (fiscalMonthStart < 1 || fiscalMonthStart > 12)
        {
            throw new ArgumentOutOfRangeException(nameof(fiscalMonthStart));
        }

        _months = months;
        _fiscalMonthStart = fiscalMonthStart;
        _index = _fiscalMonthStart - 2;
    }
    public object Current
    {
        get
        {
            if (_index < 0 || _index > _months.Length)
            {
                throw new IndexOutOfRangeException();
            }
            _count++;
            return _months[_index];
        }
    }


    public bool MoveNext()
    {
        if (_count >= 12)
        {
            return false;
        }

        _index++;
        if (_index == _months.Length)
        {
            _index = 0;
        }

        return true;
    }

    public void Reset()
    {
        _count = 0;
        _index = _fiscalMonthStart - 2;
    }
}Code language: C# (cs)

Third, use the foreach to iterate the elements of the Months collection:

public class Program
{
    public static void Main(string[] args)
    {
        var months = new Months();

        Console.WriteLine("Fiscal month start in January:");
        foreach (var month in months)
        {
            Console.Write($"{month} ");
        }

        Console.WriteLine();

        // fiscal month start in April
        Console.WriteLine("Fiscal month start in April:");
        months.FiscalMonthStart = 4;
        foreach (var month in months)
        {
            Console.Write($"{month} ");
        }
    }
}Code language: C# (cs)

Output:

Fiscal month start in January:
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
Fiscal month start in April:
Apr May Jun Jul Aug Sep Oct Nov Dec Jan Feb MarCode language: C# (cs)

Put it all together:

using System.Collections;

namespace IteratorPattern;

public class Months : IEnumerable
{
    private readonly string[] _months = {
        "Jan","Feb", "Mar",
        "Apr","May", "Jun",
        "Jul","Aug", "Sep",
        "Oct","Nov", "Dec",
    };

    public int FiscalMonthStart { get; set; } = 1;

    public IEnumerator GetEnumerator()
    {
        return new MonthIterator(_months, FiscalMonthStart);
    }
}

public class MonthIterator : IEnumerator
{
    private int _index;
    private int _count = 0;
    private readonly int _fiscalMonthStart = 0;
    private readonly string[] _months;

    public MonthIterator(string[] months, int fiscalMonthStart)
    {
        if (months.Length != 12)
        {
            throw new ArgumentException("The number of months is not 12");
        }

        if (fiscalMonthStart < 1 || fiscalMonthStart > 12)
        {
            throw new ArgumentOutOfRangeException(nameof(fiscalMonthStart));
        }

        _months = months;
        _fiscalMonthStart = fiscalMonthStart;
        _index = _fiscalMonthStart - 2;
    }
    public object Current
    {
        get
        {
            if (_index < 0 || _index > _months.Length)
            {
                throw new IndexOutOfRangeException();
            }
            _count++;
            return _months[_index];
        }
    }


    public bool MoveNext()
    {
        if (_count >= 12)
        {
            return false;
        }

        _index++;
        if (_index == _months.Length)
        {
            _index = 0;
        }

        return true;
    }

    public void Reset()
    {
        _count = 0;
        _index = _fiscalMonthStart - 2;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        var months = new Months();

        Console.WriteLine("Fiscal month start in January:");
        foreach (var month in months)
        {
            Console.Write($"{month} ");
        }

        Console.WriteLine();

        // fiscal month start in April
        Console.WriteLine("Fiscal month start in April:");
        months.FiscalMonthStart = 4;
        foreach (var month in months)
        {
            Console.Write($"{month} ");
        }
    }
}Code language: C# (cs)

Summary

  • Use the Iterator pattern to define an interface for iterating elements of a collection.
  • Use IEnumerable<T> and IEnumerator<T> interfaces to implement the iterator pattern in C#.
  • Use foreach to iterate elements in a collection that implement the IEnumerable<T> interface.
Was this tutorial helpful ?