Every time you write foreach in C#, you’re using the Iterator pattern. It’s so baked into the language that it’s easy to forget it is a pattern. CornerStore’s category tree is a nice reminder of why it exists, and of how much C# does for you behind the scenes.
| Category | Behavioral |
| Smell fixed | S16: Three copies of the same recursive tree walk |
| Code | patterns/behavioral/iterator |
The Scene
The very first step of the trading day prints the store’s catalogue as a tree:
[001] browse
Store
Footwear
Shoes
- SHOE-RUN-42 Runner Pro 89.95
- SHOE-CAS-40 Canvas Low 59.50
Kids
- SHOE-KID-30 Junior Runner 44.00
Socks
- SOCK-WOOL Wool Socks 12.00
It ends with 12 products under Store. Later, the bulk import needs every SKU in the tree. That’s three jobs, print, count and collect, all walking the same tree.
The Smell
Each of those three jobs has its own copy of the walk. Here’s counting:
public int CountProducts(Category category)
{
// SMELL S16: manual recursion over the category tree, second copy
var count = category.Products.Count;
foreach (var child in category.Children)
{
count += CountProducts(child);
}
return count;
}
And here’s collecting SKUs, which is the same shape again:
public void CollectSkus(Category category, List<string> skus)
{
// SMELL S16: manual recursion over the category tree, third copy
foreach (var product in category.Products)
{
skus.Add(product.Sku);
}
foreach (var child in category.Children)
{
CollectSkus(child, skus);
}
}
Each method mixes two things: how to walk the tree and what to do at each node. Want a fourth operation? Write the recursion a fourth time. Want to visit level by level instead of going deep first? Rewrite all of them. And a very deep tree could, in theory, overflow the stack with all that recursion.
The Idea
Iterator pulls the walking out into its own object. The iterator remembers where it is and hands you one item at a time. The caller just says “next, next, next” and does its own job with each item. It never needs to know whether the structure underneath is a list, a tree or something else entirely.
Think of a TV remote’s “next channel” button. You don’t need to know how the channels are stored or numbered. You press next, you get the next one, and you decide whether to watch it.
The Refactor
The depth-first iterator keeps its own stack of categories still to visit. No recursion:
public bool MoveNext()
{
if (!pending.TryPop(out var next))
{
current = null;
return false;
}
current = next.Category;
CurrentDepth = next.Depth;
for (var i = next.Category.Children.Count - 1; i >= 0; i--)
{
pending.Push((next.Category.Children[i], next.Depth + 1));
}
return true;
}
Look at that for loop running backwards. A stack is last-in, first-out, so pushing the children in reverse means they come back out in their original order. That tiny detail is what keeps the browse output identical to the baseline, and the golden master happy.
Counting products now just consumes the iterator:
using var iterator = new DepthFirstCategoryIterator(category);
while (iterator.MoveNext())
{
count += iterator.Current.Products.Count;
}
There’s also a BreadthFirstCategoryIterator that uses a queue instead of a stack, to visit the tree level by level. Swap one iterator for the other and every operation changes order, with no change to the operations themselves.
classDiagram
IEnumerator~Category~ <|-- ICategoryIterator
ICategoryIterator <|.. DepthFirstCategoryIterator
ICategoryIterator <|.. BreadthFirstCategoryIterator
DepthFirstCategoryIterator --> Category : stack
BreadthFirstCategoryIterator --> Category : queue
Catalog --> ICategoryIterator : traverses categories
What It Cost
6 files: about 200 lines added and 22 removed. Two iterator classes, an interface and tests for both visiting orders.
When Not to Use It
Honestly? In C#, you’ll rarely write an iterator class like this by hand.
The book was written before most languages had iterators built in. Today, C# gives you IEnumerable<T> and yield return, and the compiler writes the iterator class for you. The same depth-first walk could look like this:
public static IEnumerable<Category> DepthFirst(Category root)
{
var pending = new Stack<Category>();
pending.Push(root);
while (pending.TryPop(out var current))
{
yield return current;
for (var i = current.Children.Count - 1; i >= 0; i--)
{
pending.Push(current.Children[i]);
}
}
}
And then every operation becomes a foreach, or even a one-line LINQ query like DepthFirst(root).Sum(c => c.Products.Count).
The CornerStore solution deliberately writes IEnumerator<T> by hand, so you can see the state the compiler normally hides: the stack, the current position and the depth. That’s the pattern. yield return is the pattern with the boilerplate removed.
So: understand the explicit version, then use yield return in real code. Write a full iterator class only when you need extra state or behaviour that a generator can’t express cleanly.
Easily Confused With
Visitor. Both deal with “do something to every element of a structure”. An iterator controls how you move through the elements, and you do the work. A visitor packages the work itself as an object that each element accepts. They often work together: iterate the elements, and let each one accept a visitor. You’ll see Visitor at the end of the Behavioral patterns.
Try It Yourself
dotnet build patterns/behavioral/iterator/Iterator.slnx
dotnet test patterns/behavioral/iterator/Iterator.slnx
dotnet run --project patterns/behavioral/iterator/src/CornerStore/CornerStore.csproj -- scenarios/trading-day.json
Exercise: switch PrintTree to the breadth-first iterator and run the tests. The golden master fails, reports the first line that differs, and writes the actual output to scenarios/expected/baseline.received.txt. Compare the two files and you’ll see exactly how the visiting order changed.
The Iterator README has the participant table and a sequence diagram. The full change is in DIFF.md.
Previous: Interpreter · Next: Mediator