Iterator Pattern

The iterator design pattern is a behavioral design pattern. It is part of the Gang Of Four design patterns. The main intent of this design pattern is to provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

In simple language, what the above statement means is that the design pattern provides guidelines to segregate the implementation of a collection from its iteration.

If you think about this in terms of Solid Design Principles, it will make a lot of sense. Because the collection which is an aggregate of objects must not have the responsibility of how to iterate through the collection. That responsibility must remain with the iterator itself.

And that is what the iterator design pattern advocates. Now let us go through the implementation of this pattern to make it clearer.

Implementation of the iterator pattern

For the purpose of this implementation, there will be an aggregate. The aggregate is a class that will hold the collection. And then there will be an iterator. The iterator will be responsible for iterating through the aggregate or the collection.

Since the aggregate and the iterator are related to each other, we need to create both of them almost in parallel.

The Aggregate class in the iterator pattern

namespace Iterator.Demo;

internal interface IAggregate<T>
{
    T this[int index] { get; set; }

    int Count { get; }
    IIterator<T> Iterator { get; }
}

internal class Aggregate<T> : IAggregate<T>
{
    private IIterator<T> _iterator;
    private List<T> _list = new List<T>();

    public T this[int index]
    {
        get { return _list[index]; }
        set { _list.Add(value); }
    }

    public IIterator<T> Iterator
    {
        get
        {
            if (this._iterator == null) _iterator = new Iterator<T>(this);
            return _iterator;
        }
    }

    public int Count => _list.Count;
}

For the aggregate implementation, we will create a generic interface IAggregate. This interface will contain an indexer, a Count property, and an Iterator property. The return type of Count will be an integer and the return type of the Iterator will be IIterator.

Inside the Aggregate class, we will create a couple of local variables. The first one is an IIterator and the second one will be a generic List.

Next, we will need some way to expose the value because the iterator will need the values to iterate through. And also we will need a way to add values. We can support both through a single indexer implementation. The Get method will return the value from the List based on the index. And The Set method will call the Add method on the List object to add the value to the.

And the Iterator property will return a new instance of the generic iterator if it is not created already. But if the iterator object exists, it will just return the already available object.

And finally, we will need the count of the aggregate, which is the total number of items in the aggregate. Hence the Count property will return the total count of items in the List.

So here now the aggregate is just holding the object and the iterator will be responsible for iterating through the object.

The Iterator class in the iterator pattern

The responsibility of the iterator will be to iterate through the Aggregate. Hence for the iteration need to perform these three tasks:

  • Firstly, it will need the current value of the aggregate
  • Secondly, it will need to know if there are any items are still available on the aggregate
  • Thirdly, it will need a way to move to the next item on the aggregate

Hence to support these responsibilities of the iterator, we will create three members in the iterator interface.

The Interface

namespace Iterator.Demo;

internal interface IIterator<T>
{
    T Next();
    T Current { get; }
    bool IsLeft();
}

The first member will be the Next method, and the second member is the Current property, which can be just a Get property. And the last member is IsLeft method.

Next, we will implement the Iterator class, which will implement the IIterator interface.

The concrete class

namespace Iterator.Demo;

internal class Iterator<T> : IIterator<T>
{
    private readonly IAggregate<T> _aggregate;
    int index = 0;

    public Iterator(IAggregate<T> aggregate)
    {
        this._aggregate = aggregate;
    }

    public T Current => _aggregate[index];

    public bool IsLeft() => index < _aggregate.Count;

    public T Next()
    {
        index++;
        return IsLeft() ? _aggregate[index] : default;
    }
}

The constructor of the Iterator class will inject the IAggregate interface, as it will need it for core functionality. And we will declare a member variable in the class to hold the index of the aggregate, and its default value will be 0.

The Current property will return the current item, and for that, it will use the index to get the current item from the aggregate using its indexer.

For the IsLeft property, we just need to find out if any item is left in the aggregate. And we can achieve that easily by making sure that the Count property is greater than the current index.

Finally, for the Next implementation, we will first increment the index. And after that, if any item is left we will return the item from the aggregate at that index. If no item is left, we will return the default value of the generic type.

For the example, we will declare a record type for the data model. Hence for that, we will declare a Person record type. And it will contain two properties, Name, and Age.

internal record Person(string Name, int Age);

Using the aggregate and iterator

Now we will update the Program class to iterate through an aggregate.

using Iterator.Demo;

var persons = new Aggregate<Person>();
persons[0] = new Person("John", 30);
persons[1] = new Person("Jane", 20);
persons[3] = new Person("Michael", 10);

var iterator = persons.Iterator;

while (iterator.IsLeft())
{
    Console.WriteLine(iterator.Current);
    iterator.Next();
}

Firstly, I will create an Aggregate of type Person, and I will add three Person objects to the aggregate.

Next, I will get the Iterator from the aggregate.

And finally, through a while loop, I will use the IsLeft method to iterate through the aggregate using the iterator and print the current item.

If we run this application, we will see the code will iterate through the aggregate and it will print the three Person objects added to the aggregate.

iterator pattern

Conclusion

Using the iterator pattern it is very simple to implement an extremely generic implementation. And we can use this implementation with multiple types of objects.

The C#/.NET already provides an implementation of the iterator design pattern through IEnumerator. Tough the IEnumerator implementation in C#/.NET is more sophisticated than my implementation.

This is the high-level implementation of the iterator pattern. And there are situations where you might need to create your own iterator, in which case it will be useful. Otherwise, you can just go ahead with IEnumerator and that will work for you.

A Youtube video for this implementation is available here.