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. public int Count
  5. {
  6. get
  7. {
  8. return items.Count;
  9. }
  10. }
  11. public Story this[int index]
  12. {
  13. get
  14. {
  15. return items[index] as Story;
  16. }
  17. set
  18. {
  19. items.Insert(index, value);
  20. }
  21. }
  22. public StoryEnumerator GetEnumerator()
  23. {
  24. return new StoryEnumerator(this);
  25. }
  26. }
  27. public class StoryEnumerator
  28. {
  29. private int currentIndex = -1;
  30. private StoryCollection collection;
  31. public StoryEnumerator(StoryCollection collection)
  32. {
  33. this.collection = collection;
  34. }
  35. public Story Current
  36. {
  37. get
  38. {
  39. if (currentIndex < 0 || collection.Count <= currentIndex)
  40. return null;
  41. return collection[currentIndex];
  42. }
  43. }
  44. public bool MoveNext()
  45. {
  46. if (collection.Count == (currentIndex + 1))
  47. return false;
  48. ++currentIndex;
  49. return true;
  50. }
  51. public void Reset()
  52. {
  53. currentIndex = -1;
  54. }
  55. }
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. while (iterator.MoveNext())
  10. {
  11. Assert.Fail();
  12. }
  13. }
  14. [TestMethod]
  15. public void MoveNext_TwoElements_AddsTwo()
  16. {
  17. int sum = 0;
  18. var collection = new StoryCollection();
  19. collection[0] = new Story();
  20. collection[1] = new Story();
  21. var iterator = collection.GetEnumerator();
  22. while (iterator.MoveNext())
  23. {
  24. sum++;
  25. }
  26. Assert.AreEqual(2, sum);
  27. }
  28. [TestMethod]
  29. public void MoveNextTwice_TwoElements_AddsTwo()
  30. {
  31. int sum = 0;
  32. var collection = new StoryCollection();
  33. collection[0] = new Story();
  34. collection[1] = new Story();
  35. var iterator = collection.GetEnumerator();
  36. while (iterator.MoveNext())
  37. {
  38. sum++;
  39. }
  40. while (iterator.MoveNext())
  41. {
  42. sum++;
  43. }
  44. Assert.AreEqual(2, sum);
  45. }
  46. [TestMethod]
  47. public void MoveNextTwiceThenReset_TwoElements_AddsFour()
  48. {
  49. int sum = 0;
  50. var collection = new StoryCollection();
  51. collection[0] = new Story();
  52. collection[1] = new Story();
  53. var iterator = collection.GetEnumerator();
  54. while (iterator.MoveNext())
  55. {
  56. sum++;
  57. }
  58. iterator.Reset();
  59. while (iterator.MoveNext())
  60. {
  61. sum++;
  62. }
  63. Assert.AreEqual(4, sum);
  64. }
  65. [TestMethod]
  66. public void MoveNext_TwoElements_GetsCurrent()
  67. {
  68. int i = 0;
  69. var collection = new StoryCollection();
  70. collection[0] = new Story { Title = "0" };
  71. collection[1] = new Story { Title = "1" };
  72. var iterator = collection.GetEnumerator();
  73. while (iterator.MoveNext())
  74. {
  75. Assert.AreEqual(i.ToString(), iterator.Current.Title);
  76. ++i;
  77. }
  78. Assert.AreEqual(2, i);
  79. }
  80. }
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.