Design patterns: Iterator

An Iterator provides a way to access the elements of an object sequentially without exposing the underlying representation. It is a base Design Patterns which we use permanently. For example a loop over a list is done by using the list iterator. But of course, the Iterator pattern is not limited to simple loops over lists. You can use complex structures too and you can provide several ways to loop over elements. A complex structure can be a tree. In this case it is possible to implement an Iterator for the tree nodes. And of course there are several ways to loop over elements. Beside the standard iteration from front to back you may provide a way to loop from back to front, from a specific index to a specific, you can change the step width and so on.

The .NET Framework offers the IEnumerable and IEnumerator interfaces to implement the Iterator design pattern. Moreover the yield return keyword makes implementing this pattern even easier.

IEnumerable is an interface that defines one method GetEnumerator which returns an IEnumerator interface.  This will allow a read-only access to a collection. A collection that implements IEnumerable can be used within a foreach statement.

An IEnumerator is a thing that can enumerate. It has the Current property and the MoveNext and  Reset methods.

Within the following example we create an generic collection which provides some Iterators. In this case it is sufficient to implement the IEnumerable interface for the standard front to back iteration and to add some more iterators by providing an enumerable object using the yield return feature.

At first we create a little data class to store information of a month, in this case to keep it simple we use the month name only.

public class Month
{
    public string Name { get; set; }
}

Next, we implement a generic ItemCollection class. Internal it will store the items within a standard list. To access the data we add some methods: Add, Reset and Count. At next we implement the IEnumerable interface. For the standard iteration from start to end we can use the enumerator of the list.

public class ItemCollection<T> : IEnumerable<T>
{
    private List<T> _items = new List<T>();
        
    public void Add(T item)
    {
        _items.Add(item);
    }

    public void Reset()
    {
        _items = new List<T>();
    }

    public int Count
    {
        get
        {
            return _items.Count;
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _items.GetEnumerator();
    }
        
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
} 

Within a simple test console application we may now use our ItemCollection class.

static void Main(string[] args)
{
    ItemCollection<Month> months = new ItemCollection<Month>();
            
    months.Add(new Month() { Name = "January" });
    months.Add(new Month() { Name = "February" });
    months.Add(new Month() { Name = "March" });
    months.Add(new Month() { Name = "April" });
    months.Add(new Month() { Name = "May" });
    months.Add(new Month() { Name = "June" });
    months.Add(new Month() { Name = "July" });
    months.Add(new Month() { Name = "August" });
    months.Add(new Month() { Name = "September" });
    months.Add(new Month() { Name = "October" });
    months.Add(new Month() { Name = "November" });
    months.Add(new Month() { Name = "December" });

    Console.WriteLine("---standard---");
    foreach (Month month in months)
    {
        Console.WriteLine(month.Name);
    }

    Console.ReadKey();
}

Till now our ItemCollection class does not provide any benefit compared to a standard list. So let us add some more iterators. The following code shows the additional iterator to loop from back to front, from an index to an end index or with a specific step width.

public class ItemCollection<T> : IEnumerable<T>
{
    private List<T> _items = new List<T>();
        
    public void Add(T item)
    {
        _items.Add(item);
    }

    public void Reset()
    {
        _items = new List<T>();
    }

    public int Count
    {
        get
        {
            return _items.Count;
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _items.GetEnumerator();
    }
        
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    public IEnumerable<T> FrontToBack
    {
        get
        {
            return this;
        }
    }

    public IEnumerable<T> BackToFront
    {
        get
        {
            for (int index = Count - 1; index >= 0; index--)
            {
                yield return _items[index];
            }                
        }
    }

    public IEnumerable<T> FromTo(int fromIndex, int toIndex)
    {
        for (int index = fromIndex; index <= toIndex; index++)
        {
            yield return _items[index];
        }
    }

    public IEnumerable<T> StepWidth(int stepWidth)
    {
        for (int index = 0; index < Count; index = index + stepWidth)
        {
            yield return _items[index];
        }
    }
}  

The new iterators can be used within the foreach loop.

static void Main(string[] args)
{
    ItemCollection<Month> months = new ItemCollection<Month>();
            
    months.Add(new Month() { Name = "January" });
    months.Add(new Month() { Name = "February" });
    months.Add(new Month() { Name = "March" });
    months.Add(new Month() { Name = "April" });
    months.Add(new Month() { Name = "May" });
    months.Add(new Month() { Name = "June" });
    months.Add(new Month() { Name = "July" });
    months.Add(new Month() { Name = "August" });
    months.Add(new Month() { Name = "September" });
    months.Add(new Month() { Name = "October" });
    months.Add(new Month() { Name = "November" });
    months.Add(new Month() { Name = "December" });

    Console.WriteLine("---standard---");
    foreach (Month month in months)
    {
        Console.WriteLine(month.Name);
    }

    Console.WriteLine("---front to back---");
    foreach(Month month in months.FrontToBack)
    {
        Console.WriteLine(month.Name);
    }

    Console.WriteLine("---back to front---");
    foreach (Month month in months.BackToFront)
    {
        Console.WriteLine(month.Name);
    }

    Console.WriteLine("---from index to index---");
    foreach (Month month in months.FromTo(5,6))
    {
        Console.WriteLine(month.Name);
    }

    Console.WriteLine("---with step width---");
    foreach (Month month in months.StepWidth(3))
    {
        Console.WriteLine(month.Name);
    }

    Console.ReadKey();
}

This little example shows the principle of the Iterator design pattern. Of course, for such simple collections like the one in the example you can use the available collections provided by the .NET framework. But if you use complex structures, for example a tree, you may want implement your own Iterator patterns.

Werbung
Dieser Beitrag wurde unter .NET, C#, Clean Code, Design Pattern veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s