Design Patterns: Iterator

The Iterator Design Pattern separates the logic to navigate a collection into a different layer, so your collections won't need to understand how to iterate through its elements.

This pattern exists in a class that will hold the elements and another class that will navigate.

An example from scratch could be a collection of stories that are iterated using a StoryIterator and not by the collection itself as in the following:

  1. public class StoryCollection    
  2. {    
  3.     private ArrayList items = new ArrayList();    
  4.      
  5.     public int Count    
  6.     {    
  7.         get    
  8.         {    
  9.             return items.Count;    
  10.         }    
  11.     }    
  12.      
  13.     public Story this[int index]    
  14.     {    
  15.         get    
  16.         {    
  17.             return items[index] as Story;    
  18.         }    
  19.         set    
  20.         {    
  21.             items.Insert(index, value);    
  22.         }    
  23.     }    
  24.      
  25.     public StoryEnumerator GetEnumerator()    
  26.     {    
  27.         return new StoryEnumerator(this);    
  28.     }    
  29. }    
  30.      
  31. public class StoryEnumerator    
  32. {    
  33.     private int currentIndex = -1;    
  34.      
  35.     private StoryCollection collection;    
  36.      
  37.     public StoryEnumerator(StoryCollection collection)    
  38.     {    
  39.         this.collection = collection;    
  40.     }    
  41.      
  42.     public Story Current    
  43.     {    
  44.         get    
  45.         {    
  46.             if (currentIndex < 0 || collection.Count <= currentIndex)    
  47.                 return null;    
  48.      
  49.             return collection[currentIndex];    
  50.         }    
  51.     }    
  52.      
  53.     public bool MoveNext()    
  54.     {    
  55.         if (collection.Count == (currentIndex + 1))    
  56.             return false;    
  57.      
  58.         ++currentIndex;    
  59.      
  60.         return true;    
  61.     }    
  62.      
  63.     public void Reset()    
  64.     {    
  65.         currentIndex = -1;    
  66.     }    
  67. }    
And to use it we could test several scenarios as in the following:
  1. [TestClass]    
  2. public class IteratorTest    
  3. {    
  4.     [TestMethod]    
  5.     public void MoveNext_EmptyArray_DoesNothing()    
  6.     {    
  7.         var collection = new StoryCollection();    
  8.         var iterator = collection.GetEnumerator();    
  9.      
  10.         while (iterator.MoveNext())    
  11.         {    
  12.             Assert.Fail();    
  13.         }    
  14.     }    
  15.      
  16.     [TestMethod]    
  17.     public void MoveNext_TwoElements_AddsTwo()    
  18.     {    
  19.         int sum = 0;    
  20.                  
  21.         var collection = new StoryCollection();    
  22.         collection[0] = new Story();    
  23.         collection[1] = new Story();    
  24.      
  25.         var iterator = collection.GetEnumerator();    
  26.      
  27.         while (iterator.MoveNext())    
  28.         {    
  29.             sum++;    
  30.         }    
  31.      
  32.         Assert.AreEqual(2, sum);    
  33.     }    
  34.      
  35.     [TestMethod]    
  36.     public void MoveNextTwice_TwoElements_AddsTwo()    
  37.     {    
  38.         int sum = 0;    
  39.      
  40.         var collection = new StoryCollection();    
  41.         collection[0] = new Story();    
  42.         collection[1] = new Story();    
  43.      
  44.         var iterator = collection.GetEnumerator();    
  45.      
  46.         while (iterator.MoveNext())    
  47.         {    
  48.             sum++;    
  49.         }    
  50.      
  51.         while (iterator.MoveNext())    
  52.         {    
  53.             sum++;    
  54.         }    
  55.      
  56.         Assert.AreEqual(2, sum);    
  57.     }    
  58.      
  59.     [TestMethod]    
  60.     public void MoveNextTwiceThenReset_TwoElements_AddsFour()    
  61.     {    
  62.         int sum = 0;    
  63.      
  64.         var collection = new StoryCollection();    
  65.         collection[0] = new Story();    
  66.         collection[1] = new Story();    
  67.      
  68.         var iterator = collection.GetEnumerator();    
  69.      
  70.         while (iterator.MoveNext())    
  71.         {    
  72.             sum++;    
  73.         }    
  74.      
  75.         iterator.Reset();    
  76.      
  77.         while (iterator.MoveNext())    
  78.         {    
  79.             sum++;    
  80.         }    
  81.      
  82.         Assert.AreEqual(4, sum);    
  83.     }    
  84.      
  85.      
  86.     [TestMethod]    
  87.     public void MoveNext_TwoElements_GetsCurrent()    
  88.     {    
  89.         int i = 0;    
  90.      
  91.         var collection = new StoryCollection();    
  92.         collection[0] = new Story { Title = "0" };    
  93.         collection[1] = new Story { Title = "1" };    
  94.      
  95.         var iterator = collection.GetEnumerator();    
  96.      
  97.         while (iterator.MoveNext())    
  98.         {    
  99.             Assert.AreEqual(i.ToString(), iterator.Current.Title);    
  100.             ++i;    
  101.         }    
  102.      
  103.         Assert.AreEqual(2, i);    
  104.     }    
  105. }    
The only thing the collection does is worry about storing items. This example shows how it would look from scratch, but in .NET we already have the IEnumerable<T> and IEnumerator<T> interfaces for that purpose. All commonly-used collections already implement those interfaces. If a class implements these interfaces then the collection will be available on a foreach loop.