In this lesson, you will learn how to demystify the "magic" behind foreach by building a custom iterator that manually implements the IEnumerable and IEnumerator interfaces.
The Contract of Iteration
At its core, C# doesn't use magic to traverse collections; it uses a strict architectural handshake.
IEnumerable: This is the "factory." Its job is simply to produce anIEnumerator. It represents a collection that can be traversed.IEnumerator: This is the "worker." It maintains the internal state of your position within the collection and knows how to move to the next item.
Building the Mechanics
To understand this, imagine we want to iterate over a simple range of numbers. Instead of using a List, we build a class that tells the compiler how to move through our data manually:
public class NumberIterator : IEnumerable<int>
{
public IEnumerator<int> GetEnumerator()
{
// We yield the items one by one
yield return 1;
yield return 2;
yield return 3;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
When you write foreach (var n in new NumberIterator()), the compiler translates your code into a while loop that calls GetEnumerator() to get the worker, then repeatedly calls MoveNext() on that worker to update the Current property.
Why This Matters
The yield keyword is actually a shorthand that generates a state machine for you. If you were to write the IEnumerator by hand, you would have to maintain an _index variable and a Reset() method. By understanding that foreach is just a high-level wrapper around the MoveNext() / Current pattern, you stop viewing collections as static piles of data and start seeing them as active streams.
This architecture allows C# to handle infinite sequences and memory-efficient data processing, as the "worker" only ever calculates the next step when you explicitly ask for it.
Key Takeaway: IEnumerable is the blueprint for a collection, while IEnumerator is the stateful cursor that performs the actual traversal.