SkillByAIOpen interactive version →

Lesson 15 / 25

IEnumerable and yield return

Write lazy sequences with iterators and avoid multiple enumeration.

Sequences produced on demand

IEnumerable<T> represents a sequence you can walk once from the start with an enumerator (MoveNext() and Current). An iterator method uses yield return to produce items one at a time; the compiler turns it into a state machine that pauses after each yield and resumes when the caller asks for the next item. That makes sequences lazy: you can model huge or even infinite data, such as lines of a large file, without loading everything. yield break ends the sequence. Laziness has two consequences. First, code inside the iterator, including argument validation, does not run until enumeration starts. Second, enumerating an IEnumerable<T> twice runs the work twice, which may hit a database or file twice or produce different results; materialise with ToList() when you need several passes.

A lazy file reader

Only one line at a time is held in memory; Take(5) stops reading after five matches.

IEnumerable<string> ErrorLines(string path)
{
    foreach (var line in File.ReadLines(path))   // ReadLines is itself lazy
    {
        if (line.Contains("ERROR", StringComparison.Ordinal))
            yield return line;
    }
}

foreach (var line in ErrorLines("app.log").Take(5))
    Console.WriteLine(line);

IEnumerable<long> Fibonacci()
{
    long a = 0, b = 1;
    while (true)
    {
        yield return a;
        (a, b) = (b, a + b);
    }
}

var firstTen = Fibonacci().Take(10).ToList();

A tap, not a bucket

A List<T> is a bucket of water you carry around. An iterator is a tap: water flows only while you hold your glass under it, and turning it on again runs fresh water through the pipes.

Quick check: Why can enumerating the same IEnumerable<T> twice be a problem?

  • It never compiles
  • The second enumeration is always empty
  • The underlying work, such as a database query or file read, may run twice
  • It converts the sequence to an array
Answer

The underlying work, such as a database query or file read, may run twice — Lazy sequences re-execute on each enumeration unless materialised.