Patterns. The Iterator pattern. Implementation in C#

The Iterator pattern. Implementation in C#

Before studying this topic, it is recommended that you familiarize yourself with the topic:


Contents


Search other websites:




1. The structure of the Iterator pattern in C# codes. Picture

Information about the Iterator pattern and how to implement it can be found here. Figure 1 shows the structure of the Iterator pattern with C# code snippets. This structure can be used for any number of containers (polymorphic container) and any number of iterators (polymorphic iterator).

In order to add your own specific container, you need to implement the IAggregate interface in the class. This interface declares a single method, CreateIterator(). This method returns an iterator for the container.

Patterns. C#. The structure of Iterator pattern.

Figure 1. The structure of Iterator pattern

 

2. Programming the components that define the structure of the Iterator pattern

To implement the Iterator pattern, you need to develop the following interfaces and classes:

  • an IAggregate interface containing declarations for the CreateIterator() method;
  • the IIterator interface, which contains basic methods for getting an iterator and moving around a container;
  • one or more specific iterators. Here iterators can be declared that perform various traversal strategies. All iterators implement the IIterator interface;
  • one or more classes that implement the IAggregate interface.

 

2.1. Interfase IAggregate<T> of the iterator

First of all, the container interface IAggregate<T> is declared, containing the declarations of the following methods:

  • CreateIterator() – returns an iterator for the container;
  • Count() – returns the number of items in the container;
  • GetItem() – returns a container item by its index.

According to C# syntax, all of these methods must be implemented in classes that implement the IAggregate<T> interface.

After entering the interface, the program listing is as follows:

using System;

namespace ConsoleApp17
{
  // Implementation of Iterator pattern in C#.
  // Example for a console application
  // 1. Declare container interface supporting generic type T
  interface IAggregate<T>
  {
    // Return iterator to a container
    IIterator<T> CreateIterator();

    // The number of elements of the container
    long Count();

    // Return an element by its index
    T GetItem(long index);
  }

  class Program
  {
    static void Main(string[] args)
    {
    }
  }
}

The interface serves as a glue between the subclasses, which is what the IIterator<T> interface implements.

 

2.2. The interface IIterator<T> of iterator

To provide the ability to implement various kinds of iterators, you need to declare the iterator interface IIterator<T>. This interface will contain common methods for all iterator classes that will implement it.

In our case, the following method declarations are introduced into the IIterator<T> interface:

  • First() – go to the first item of the container;
  • Next() – move the cursor to the next item of the container;
  • IsDone() – check if the list is end;
  • CurrentItem() – get the current element at the cursor.

Optionally, you can extend the list of methods for inherited iterators. For example, add the Previous() method to navigate to the previous item in the container.

The abbreviated program code after entering the interface is as follows.

using System;

namespace ConsoleApp17
{
  // Implementation the Iterator pattern in C#.
  // Example for console application
  // 1. Declare container interface supporting generic T type
  interface IAggregate<T>
  {
    ...
  }

  // 2. Declare an iterator interface for type T
  interface IIterator<T>
  {
    // Move to the first element of the container
    void First();

    // Move to the next element of the container
    void Next();

    // Get the current item
    T CurrentItem();

    // Checking if the cursor is pointing to the end of the container
    bool IsDone();
  }

  ...
}

 

2.3. Concrete iterator ConcreteIterator<T>

This step creates one or more classes that implement the IIterator<T> interface. In our case, we create one class named ConcreteIterator1<T>. This class implements a forward iterator.

The following program elements are implemented in a specific iterator:

  • the internal field aggregate is a reference to the container for which this iterator is being created;
  • the internal field current is a value (position) that points to one of the container elements. This is a cursor;
  • a constructor that takes an aggregate object as a parameter;
  • the First(), Next(), IsDone() and CurrentItem() methods, which override the IIterator<T> interface methods.
using System;

namespace ConsoleApp17
{
  // Implementation of Iterator pattern in C#.
  // Example for console application
  // 1. Declare container interface supporting generic T type
  interface IAggregate<T>
  {
    ...
  }

  // 2. Declare an iterator interface for type T
  interface IIterator<T>
  {
    ...
  }

  // 3. Concrete iterator
  class ConcreteIterator1<T> : IIterator<T>
  {
    // internal data
    private IAggregate<T> aggregate; // A reference to aggregate
    private long current; // current position

    // Constructor taking an aggregate object
    public ConcreteIterator1(IAggregate<T> obj)
    {
      aggregate = obj;
      current = 0;
    }

    // Move cursor to the beginning of the list
    virtual public void First()
    {
      current = 0;
    }

    // Move the cursor to the next item in the list
    virtual public void Next()
    {
      current++;
    }

    // Check whether the end of the list
    virtual public bool IsDone()
    {
      return current >= aggregate.Count();
    }

    // Return the current list item
    virtual public T CurrentItem()
    {
      if (!IsDone())
        return aggregate.GetItem(current);
      else
      {
        throw new NotImplementedException("Error");
      }
    }
  }

  ...
}

Using the above code sample, you can add other kinds of iterators with arbitrary names. A prerequisite is the implementation (inheritance) of the IIterator<T> interface. If you need to add your own iterator, then the following steps are performed:

  • choose the name of the iterator and declare the class of this iterator (for example, ConcreteIterator2<T> or something else). Класс должен реализовывать интерфейс IIterator<T>;
  • implement internal data and one or more constructors;
  • implement the methods First(), Next(), IsDone(), CurrentItem() declared in the IIterator<T> interface;
  • optionally declare additional methods.

One type of iterator is a reverse iterator, which loops through the container from end to beginning. In our case, if it were necessary to implement a reverse iterator (it looks through the elements of the container from end to beginning), then some methods should have a different interpretation:

  • the First() method would implement a jump to the last element of the container;
  • the Next() method would move the cursor to the previous element in the container.

 

2.4. The ConcreteAggregate<T> class

The IAggregate<T> interface declared in clause 2.1 is common to all classes that implement a specific aggregate (container). Thanks to polymorphism, any number of specific aggregates can be implemented in a program. As you know, in each container class, data is represented as a set of elements (list, array, tree, etc.).

If a class of a specific container (aggregate) is created, then a number of requirements are imposed on it:

  • the class must implement (inherit) the IAggregate<T> interface;
  • the class must contain the minimum functionality for operating with a set of elements;
  • the class must implement methods of the IAggregate<T> interface. The required method is the CreateIterator() method.

In our case, the ConcreteAggregate1<T> class is declared, which implements the IAggregate<T> interface. The class contains the following main components:

  • internal array field array of generic type T. Any other data structure can be used instead of array: list, tree, set, and the like;
  • two constructors;
  • Append() method – adds a new item to the end of the array;
  • Remove() method – removes an element from an array at a specified position;
  • method Print() – displays the array;
  • methods declared in the IAggregate<T> interface: CreateIterator(), Count(), GetItem().

The abbreviated program fragment with the code of the ConcreteAggregate1<T> class looks like.

 

using System;

namespace ConsoleApp17
{
  // Implementation of Iterator pattern in C#.
  // Example for a console application

  // 1. Declare container interface supporting generic T type
  interface IAggregate<T>
  {
    ...
  }

  // 2. Declare an iterator interface for type T
  interface IIterator<T>
  {
    ...
  }

  // 3. Concrete iterator
  class ConcreteIterator1<T> : IIterator<T>
  {
    ...
  }

  // 4. Concrete container for items of type T
  class ConcreteAggregate1<T>:IAggregate<T>
  {
    // Internal fields
    private T[] array; // dynamic array of items of T type

    // Constructors
    // Constructor that receives an external array
    public ConcreteAggregate1(T[] _array)
    {
      array = _array;
    }

    // Constructor creating an empty array
    public ConcreteAggregate1()
    {
      array = null;
    }

    // The method that adds the element to the end of the container
    public void Append(T item)
    {
      T[] array2 = array;
      array = new T[array2.Length + 1];
      array2.CopyTo(array, 0);
      array[array.Length - 1] = item;
    }

    // Removes the element from the container at position index
    public void Remove(long index)
    {
      T[] array2 = array;
      array = new T[array2.Length - 1];
      for (long i = 0; i < index; i++)
        array[i] = array2[i];
      for (long i = index + 1; i < array2.Length; i++)
        array[i - 1] = array2[i];
    }

    // Display the contents of the container
    public void Print(string text)
    {
      Console.WriteLine(text + ":");
      for (int i = 0; i < array.Length; i++)
        Console.Write(array[i] + " ");
      Console.WriteLine();
    }

    // Implementation of methods of IAggregate<T> interface
    // Return iterator
    public IIterator<T> CreateIterator()
    {
      return new ConcreteIterator1<T>(this);
    }

    // Number of items
    public long Count()
    {
      return array.Length;
    }

    // Return the item
    public T GetItem(long index)
    {
      if ((index >= 0) && (index < array.Length))
        return array[index];
      else
        throw new NotImplementedException("Error. Bad index");
    }
  }
}

 

3. Client code. Demonstration of the pattern

Below is the client code that demonstrates how to get an iterator and how to use it.

using System;

namespace ConsoleApp17
{
  // Implementation of Iterator pattern in C#.
  // Example for console application

  // 1. Declare container interface supporting generic T type
  interface IAggregate<T>
  {
    ...
  }

  // 2. Declare an iterator interface for type T
  interface IIterator<T>
  {
    ...
  }

  // 3. Concrete iterator
  class ConcreteIterator1<T> : IIterator<T>
  {
    ...
  }

  // 4. Concrete container for items of type T
  class ConcreteAggregate1<T>:IAggregate<T>
  {
    ...
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Testing the Iterator pattern - client code
      // 1. Create an array of numbers
      double[] A = { 2.3, 3.2, 4.4, 5.1, 8.2 };

      // 2. Create container instance based on array A
      ConcreteAggregate1<double> ag1 = new ConcreteAggregate1<double>(A);
      ag1.Print("ag1"); // display the container

      // 3. Create iterator for ag1 instance
      ConcreteIterator1<double> it1 = (ConcreteIterator1<double>)ag1.CreateIterator();

      // 4. Demonstration of the iterator operation
      Console.WriteLine("----------------------");
      Console.WriteLine("Access using iterator:");

      it1.First();
      while (!it1.IsDone())
      {
        Console.Write("{0} ", (double)it1.CurrentItem());
        it1.Next();
      }

      Console.WriteLine();
      Console.ReadKey();
    }
  }
}

After starting, the program produces the following result

ag1:
2.3 3.2 4.4 5.1 8.2
----------------------
Access using iterator:
2.3 3.2 4.4 5.1 8.2

 


Related topics