Iterators are how LINQ and streaming code process large or infinite data without loading it all.
Anything you can foreach over implements IEnumerable<T>, which hands out an IEnumerator<T> with MoveNext() and Current. Writing enumerators by hand is tedious; iterator methods let the compiler do it.
A method returning IEnumerable<T> that contains yield return becomes an iterator. The compiler turns it into a state machine: nothing runs when you call the method; each MoveNext() runs the body until the next yield return, hands out that value and pauses. yield break ends the sequence.
Consequences:
- Sequences are lazy and can be infinite:
Naturals().Take(5)is fine. - Each enumeration restarts the method from the beginning.
- Argument validation in an iterator runs only on the first
MoveNext(), not at the call. To validate eagerly, put checks in a normal wrapper method that returns the result of a private local iterator function. finallyblocks andusinginside iterators run when enumeration ends, including when aforeachbreaks early (the enumerator is disposed).
IAsyncEnumerable<T> is the async counterpart: an async method with yield return produces items consumed with await foreach. It's ideal for streaming database rows or paged API results.
This is exactly how LINQ's Where and Select are implemented, which is why they are deferred.
Example
static IEnumerable<int> Naturals()
{
for (int n = 1; ; n++)
{
Console.Write($"[gen {n}] ");
yield return n;
}
}
foreach (var x in Naturals().Where(n => n % 2 == 0).Take(2))
Console.Write($"{x} "); // [gen 1] [gen 2] 2 [gen 3] [gen 4] 4
Console.WriteLine();
static async IAsyncEnumerable<string> PagesAsync()
{
for (int page = 1; page <= 3; page++)
{
await Task.Delay(10); // e.g. an HTTP call
yield return $"page {page}";
}
}
await foreach (var p in PagesAsync())
Console.Write(p + "; "); // page 1; page 2; page 3;Remember
- yield return turns a method into a lazy state-machine iterator
- Nothing runs until enumeration; each enumeration restarts
- Validate arguments eagerly in a non-iterator wrapper
- IAsyncEnumerable + await foreach stream async data
Watch out for
- Expecting an exception from argument checks at call time in an iterator method.