Course contentsShow
← C#
Lesson 196 of 1,9082. Object-Oriented Design in C#Free lesson

Iterator Pattern for Custom Collections

You will traverse complex data structures using a uniform interface regardless of underlying representation.

Mastering the Iterator Pattern for Custom Collections

In this lesson, you will learn how to traverse complex data structures using a uniform interface regardless of how the underlying data is stored.

The Problem of Hidden Structure

Imagine you have a Library class that stores books. Internally, you might use a simple List<Book>, a complex BinaryTree<Book>, or even a linked structure. If you want to allow someone to loop through your library using a foreach statement, you shouldn't force them to understand your internal structure. If you change your storage from a List to a Tree, you shouldn't have to break all the code that loops through your books.

The Uniform Solution

The Iterator pattern solves this by providing a standard way to access elements sequentially. In C#, we use the IEnumerable and IEnumerator interfaces to create this "bridge." By implementing these, you essentially tell the outside world: "I don't care how I store my data, but here is a simple way for you to walk through it one by one."

Consider this simplified implementation:

public class Library : IEnumerable<Book>
{
    private Book[] _books = { new Book("C# Basics"), new Book("Design Patterns") };

    public IEnumerator<Book> GetEnumerator()
    {
        // We provide a way to walk through our internal array
        foreach (var book in _books)
        {
            yield return book;
        }
    }

    // Explicit implementation for older compatibility
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
        => GetEnumerator();
}

By adding the yield return keyword, C# automatically generates an iterator object for you. Now, any consumer can use a standard foreach loop:

Library myLibrary = new Library();
foreach (var book in myLibrary)
{
    Console.WriteLine(book.Title);
}

The user of your Library class doesn't need to know if you are using an array, a list, or a complex graph. The traversal logic is hidden behind the GetEnumerator interface, keeping your collection logic decoupled from the code that processes the data.

Key Takeaway: The Iterator pattern allows you to provide a consistent foreach access experience to your custom collections while keeping their internal storage structure completely private and swappable.