Using the Iterator Pattern in C# ASP .NET

modified

Introduction

The Iterator design pattern provides us with a common method of enumerating a list of items or array, while hiding the details of the list’s implementation. This provides a cleaner use of the array object and hides unneccessary information from the client, ultimately leading to better code-reuse, enhanced maintainability, and fewer bugs. The iterator pattern can enumerate the list of items regardless of their actual storage type.

The Iterator design pattern is actually a quite common pattern in most object oriented programming languages, especially in C# and Java. In fact, both languages include their own pre-built Iterator pattern, which can be inherited from. Of course, knowing how to create your own Iterator design pattern can provide great benefits and allow you to expand your design when needed.

This article will describe three ways to traverse a list of items, from simply walking the array, to using the built-in C# ASP .NET IEnumerable interface, to finally creating your own home-grown Iterator design pattern.

Iterator Design Pattern C# ASP .NET

In the Beginning, There was an Array

We’ll start this article off with a class, which holds internal data in the form of a list. In this particular case, the list will be an array of objects. Specifically, we’ll use the C# .NET System.Collections.Generic type List<> as our array. It is important to note that any form of array structure can be used to hold the internal data, since we’ll be creating an Iterator to enumerate the items, regardless of type.

Let’s begin by creating a class called GoldArray. GoldArray holds an internal list of items of the type ItemContents. An ItemContents object simple has a name and description. Our GoldArray class manages this list of ItemContents by allowing us to Get, Add, and Remove objects from its internal list. While our internal list happens to be of type List<ItemContents>, it could certainly be any other collection type, such as a Hashtable<key, ItemContents>, a basic array ItemContents[10], etc. The class is defined as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class GoldArray
{
    private List<ItemContents> goldList = new List<ItemContents>();

    public int Add(string name, string description)
    {
        goldList.Add(new ItemContents(name, description));
        return goldList.Count;
    }

    public int Add(ItemContents itemContents)
    {
        goldList.Add(itemContents);
        return goldList.Count;
    }

    public ItemContents GetItem(int index)
    {
        if (index < goldList.Count)
        {
            return goldList[index];
        }
        else
        {
            return null;
        }
    }

    public List<ItemContents> GetItems()
    {
        return goldList;
    }

    public bool RemoveItem(int index)
    {
        if (index < goldList.Count)
        {
            goldList.RemoveAt(index);
            return true;
        }
        else
        {
            return false;
        }
    }
}

The ItemContents object is a simple C# ASP .NET class defined as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ItemContents
{
    public string Name;
    public string Description;

    public ItemContents(string name, string description)
    {
        Name = name;
        Description = description;
    }

    public override string ToString()
    {
        return Name + " - " + Description;
    }
}

To traverse the items in our GoldArray class, we can simply walk the array of ItemContents objects that exist in the internal array by calling GoldArray.GetItems() to get the list. This can be done as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void Main(string[] args)
{
    GoldArray MyGold = new GoldArray();
    MyGold.Add(new ItemContents("Gold Bar", "A shiny golden bar."));
    MyGold.Add(new ItemContents("Golden Coin", "A brand new coin."));
    MyGold.Add(new ItemContents("Golden Statue", "A fine golden figurine."));

    // Using no iterator.
    List<ItemContents> items = MyGold.GetItems();
    foreach (ItemContents item in items)
    {
        Console.WriteLine(item);
    }

    Console.ReadKey();
}

Output:

1
2
3
Gold Bar - A shiny golden bar.
Golden Coin - A brand new coin.
Golden Statue - A fine golden figurine.

As you can see in the ouput, by calling GetItems() to access the internal list, we can easily traverse the array using the C# .NET foreach command. While this certainly works, the problem with this method is that we’re exposing GoldArray’s internal data structures, private data which should really only be accessed by itself. Through the GetItems() function, we can easily tell that GoldArray is using a List as its storage base. If we could come up with a way to hide the type of data, we can better encapsulate GoldArray and increase maintainability. This is where the Iterator design pattern comes in.

A Home-Grown Iterator Pattern

We can improve the design above by creating our own Iterator design pattern, which will allow us to enumerate the items in GoldArray without exposing the internal implementation details. This is the perfect way to decouple GoldArray’s internal data from the client code. To do this, we’ll first need a common interface:

1
2
3
4
5
public interface ICustomIterator
{
    bool HasNext();
    object Next();
}

This is the standard interface definition for the Iterator design pattern. We simply define a HasNext() function, which tells us if more item exists, and a Next() function, which returns the next object. Note, since we define Next() as returning an object, we’ll need to cast the resulting value to our desired type while enumerating. However, this has the added advantage of code-reuse, as we can re-use this interface for any type of class.

To put our new ICustomIterator interface to use, we’ll first need to create an implementation of the interface for our GoldArray class. We’ll call this GoldArrayIterator1 (the 1 will differentiate this iterator implementation from the one we’ll make using .NET’s built-in iterator design pattern a bit later).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class GoldArrayIterator1 : ICustomIterator
{
    private List<ItemContents> _itemsList = new List<ItemContents>();
    int position = 0;

    public GoldArrayIterator1(List<ItemContents> itemsList)
    {
        _itemsList = itemsList;
    }

    #region ICustomIterator Members

    public bool HasNext()
    {
        if (position < _itemsList.Count)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public object Next()
    {
        ItemContents itemContents = _itemsList[position];
        position++;
        return itemContents;
    }

    #endregion
}

The above iterator implementation begins by holding a private list of ItemContents, just like the GoldArray class does. This list is initialized in the constructor so that it holds a copy of the items from GoldArray. We then fill in the body definitions for the interface. As you can see in the code above, HasNext() simply checks if the internal index is less than the total items in the list. The Next() function simply returns the item at the current position and increments the position. These two functions allow us to traverse the array. It’s important to note that since our iterator class implements the ICustomIterator interface, our client code can refer to ICustomIterator and never need to know the details behind the GoldArrayIterator1 class itself, or GoldArray’s internal data. This effectively decouples the client code from the objects.

We now need to add one more function to our GoldArray class so that it can provide us with an ICustomIterator to enumerate its data, as follows:

1
2
3
4
5
6
7
8
9
public class GoldArray
{
    // .... Other functions defined above in the original class ....

    public ICustomIterator CreateIterator()
    {
        return new GoldArrayIterator1(goldList);
    }
}

The new function CreateIterator(), simple returns an implementation of the ICustomIterator interface and initializes it with a copy of GoldArray’s internal list. We can actually remove the GetItems() function from GoldArray, since we no longer need it.

We can now enumerate the list of items in our client code, completely de-coupled from GoldArray, as shown in the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void Main(string[] args)
{
    GoldArray MyGold = new GoldArray();
    MyGold.Add(new ItemContents("Gold Bar", "A shiny golden bar."));
    MyGold.Add(new ItemContents("Golden Coin", "A brand new coin."));
    MyGold.Add(new ItemContents("Golden Statue", "A fine golden figurine."));

    // Using our home-grown iterator.
    ICustomIterator iterator = MyGold.CreateIterator();
    while (iterator.HasNext())
    {
        ItemContents item = (ItemContents)iterator.Next();
        Console.WriteLine(item);
    }

    Console.ReadKey();
}

Notice how we ask GoldArray to create an iterator for us. We then use the iterator in a while loop, checking the HasNext() function each time. Our only reference is to the generic C# .NET interface ICustomIterator. At this point, you should be able to see how we could create a variety of classes which implement ICustomIterator and could be enumerated in the exact same way. In fact, this is exactly how C# ASP .NET’s own built-in iterator design pattern, IEnumerable, works.

Using C# .NET IEnumerable and IEnumerator

In the code samples above, we’ve learned how to create our very own custom iterator design pattern to enumerate a list of items. This is an important pattern to know, should you find yourself in a programming language without a default implementation. However, C# ASP .NET happens to include its own iterator in the IEnumerable interface. This interface uses another interface, IEnumerator, to traverse a list. To use C#’s version of the iterator design pattern, we’ll need to create an implementation of the IEnumerable interface, just like we did for our own ICustomIterator interface. We’ll call this new iterator class, GoldArrayIterator2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class GoldArrayIterator2 : IEnumerator<ItemContents>
{
    private int _index = -1;
    private List<ItemContents> _itemContents = new List<ItemContents>();

    public GoldArrayIterator2(List<ItemContents> itemContents)
    {
        _itemContents = itemContents;
    }

    #region IEnumerator<ItemContents> Members

    public ItemContents Current
    {
        get
        {
            try
            {
                return _itemContents[_index];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    {
        _itemContents.Clear();
    }

    #endregion

    #region IEnumerator Members

    object System.Collections.IEnumerator.Current
    {
        get
        {
            return _itemContents[_index];
        }
    }

    public bool MoveNext()
    {
        _index++;
        return (_index < _itemContents.Count);
    }

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

    #endregion
}

While the code looks a bit more complicated, it’s actually pretty simple, and very similar to our own ICustomIterator design pattern. We still carry an internal copy of the list of items, defined as _itemContents. We also still carry an index pointer into the array, to indicate our current position. Our constructor is the same, in that it copies the list of items from GoldArray. The main difference is really just the names of the functions. Instead of our custom iterator’s Next() function, which returned an object and advanced the index, C# provides a separate function MoveNext() just for advancing the index, and another function Current, for accessing the current object. Our custom iterator class combined the two in a single Next() function.

Just as we did with our ICustomIterator, we still need to define a CreateIterator() function in the GoldArray class, except the naming in C# .NET is GetEnumerator(), as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class GoldArray : IEnumerable<ItemContents>
{
    // .... Other functions defined above in the original class ....

    #region IEnumerable<ItemContents> Members

    public IEnumerator<ItemContents> GetEnumerator()
    {
        return new GoldArrayIterator2(goldList);
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return new GoldArrayIterator2(goldList);   
    }

    #endregion
}

The new changes to our GoldArray class mainly include implementing the IEnumerable interface. This requires us to define a GetEnumator() function, just as we did with our own custom iterator pattern. Instead of returning a new GoldArrayIterator1(list) object, we now return a new GoldArrayIterator2(list) object.

As with our custom iterator pattern, we can remove the GetItems() function from GoldArray, since we no longer need it.

The real beauty of using C# ASP .NET’s built-in iterator design pattern comes when using it in the client code. We can take advantage of C# .NET’s foreach command to enumerate the list of items, without accessing the internal details (ie. avoiding tight coupling of classes), as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main(string[] args)
{
    GoldArray MyGold = new GoldArray();
    MyGold.Add(new ItemContents("Gold Bar", "A shiny golden bar."));
    MyGold.Add(new ItemContents("Golden Coin", "A brand new coin."));
    MyGold.Add(new ItemContents("Golden Statue", "A fine golden figurine."));

    // Using IEnumerable.
    foreach (ItemContents item in MyGold)
    {
        Console.WriteLine(item);
    }

    Console.ReadKey();
}

You may notice that the above code looks very similar to our initial starting code example at the top of this article, which looked like the following:

1
2
3
4
5
6
// Using no iterator.
List<ItemContents> items = MyGold.GetItems();
foreach (ItemContents item in items)
{
    Console.WriteLine(item);
}

The key difference between these two code segements is that one uses an iterator and one does not. The code directly above, which doesn’t use an iterator, is directly coupled with GoldArray in that it calls GetItems() and has to work with a List object. If GoldArray managed its data with a basic array, the client code directly above would have to work with an array object. The previous code, which uses C# .NET’s built-in iterator pattern, can enumerate GoldArray’s items regardless of changes to GoldArray’s internal array type. The client code could always remain “foreach (ItemContents item in MyGold)” and this is how we retain loosely-coupled classes within our iterator design pattern.

Not a Believer Yet?

We could even create another class, which uses a completely different array structure, and use the same Iterator design pattern to traverse its items. Assume we have the following new class called SilverArray, which uses a basic array:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class SilverArray : IEnumerable<ItemContents>
{
    private ItemContents[] silverList = new ItemContents[10];
    private int index = 0;

    public int Add(string name, string description)
    {
        silverList[index++] = new ItemContents(name, description);
        return index;
    }

    public int Add(ItemContents itemContents)
    {
        silverList[index++] = itemContents;
        return index;
    }

    public ItemContents GetItem(int index)
    {
        if (index < this.index)
        {
            return silverList[index];
        }
        else
        {
            return null;
        }
    }

    public ItemContents[] GetItems()
    {
        return silverList;
    }

    #region IEnumerable<ItemContents> Members

    public IEnumerator<ItemContents> GetEnumerator()
    {
        return new SilverArrayIterator2(silverList);
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return new SilverArrayIterator2(silverList);   
    }

    #endregion
}

We have one piece of work, which is to create the SilverArrayIterator, just as we did for the GoldArray class. Remember, the iterator implementation knows the details of how to traverse SilverArray’s data:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class SilverArrayIterator2 : IEnumerator<ItemContents>
{
    private int _index = -1;
    private ItemContents[] _itemContents = new ItemContents[10];

    public SilverArrayIterator2(ItemContents[] itemContents)
    {
        _itemContents = itemContents;
    }

    #region IEnumerator<ItemContents> Members

    public ItemContents Current
    {
        get
        {
            try
            {
                return _itemContents[_index];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    {
    }

    #endregion

    #region IEnumerator Members

    object System.Collections.IEnumerator.Current
    {
        get
        {
            return _itemContents[_index];
        }
    }

    public bool MoveNext()
    {
        _index++;
        return (_index < 10 && _itemContents[_index] != null);
    }

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

    #endregion
}

Note, the difference between GoldArrayIterator2 and SilverArrayIterator2 is that GoldArrayIterator uses a List object and SilverArrayIterator relies on a basic array. However, since both implement the C# .NET IEnumerator interface, our client code doesn’t know the difference:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static void Main(string[] args)
{
    GoldArray MyGold = new GoldArray();
    MyGold.Add(new ItemContents("Gold Bar", "A shiny golden bar."));
    MyGold.Add(new ItemContents("Golden Coin", "A brand new coin."));
    MyGold.Add(new ItemContents("Golden Statue", "A fine golden figurine."));

    SilverArray MySilver = new SilverArray();
    MySilver.Add(new ItemContents("Silver Bar", "A dull silver bar."));
    MySilver.Add(new ItemContents("Silver Pearl", "A perfect round silver pearl."));
    MySilver.Add(new ItemContents("Silver Cup", "A sturdy silver cup."));

    // Using IEnumerable.
    foreach (ItemContents item in MyGold)
    {
        Console.WriteLine(item);
    }

    foreach (ItemContents item in MySilver)
    {
        Console.WriteLine(item);
    }

    Console.ReadKey();
}

Notice in the client code example above, we use the same exact foreach loop to traverse the elements, even though MyGold and MySilver use a different internal array structure. The client never knows the difference. If changes were made inside GoldArray (and to its GoldArrayIterator), the client code would still remain the same.

Conclusion

The Iterator design pattern is a powerful pattern for traversing lists of objects while retaining loosely-coupled code between classes. The iterator pattern allows us to maximize code reuse and maintability through a common interface. As the iterator pattern is common in object-oriented programming languages, such as C# and Java, we can take advantage of default implementations in our language of choice. By using the iterator design pattern in our own C# ASP .NET web applications, we can enhance the lifetime of our code and the power of our applications.

About the Author

This article was written by Kory Becker, software developer and architect, skilled in a range of technologies, including web application development, machine learning, artificial intelligence, and data science.

Share