<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. C#
  3. Advanced C#
All lessons in this course

Lesson 47 of 60 12 minAdvanced

Iterators and yield

Produce sequences lazily with yield return: how iterators pause and resume, why that saves memory, and how LINQ is built on them.

foreach works on anything that implements IEnumerable<T>. You can create such sequences yourself without writing an enumerator class: a method that uses yield return becomes an iterator, producing its values one at a time, only when asked.

Interactive

yield return: one value at a time

Iterator.cs
static IEnumerable<int> Squares(int max){    for (int i = 1; i <= max; i++)    {        Console.WriteLine($"  making {i * i}");        yield return i * i;    }} foreach (int sq in Squares(5).Take(3))    Console.WriteLine($"got {sq}");
Squares() running foreach body running
Step 1 / 11
Calling Squares(5) runs none of its code: it just returns an iterator object. foreach asks it for the first value (MoveNext).

Writing an iterator

GridLines.cs
static IEnumerable<double> GridPositions(double start, double spacing, int count){    for (int i = 0; i < count; i++)        yield return start + i * spacing;   // hand out one value, then pause} foreach (double x in GridPositions(0, 6.0, 5))    Console.Write($"{x} ");
Output
0 6 12 18 24
  • The return type must be IEnumerable<T> (or IEnumerator<T>, or IAsyncEnumerable<T>).
  • Calling the method runs none of its code; it returns an iterator object.
  • Each MoveNext (each foreach step) runs until the next yield return, then pauses with all local variables kept.
  • yield break; ends the sequence early.

Infinite and huge sequences

Lazy.cs
static IEnumerable<long> Fibonacci(){    long a = 0, b = 1;    while (true)                    // infinite, but only computed on demand    {        yield return a;        (a, b) = (b, a + b);    }} Console.WriteLine(string.Join(", ", Fibonacci().Take(10))); // Read a 5 GB log, matching lines only, with tiny memory usestatic IEnumerable<string> ErrorLines(string path){    foreach (string line in File.ReadLines(path))        if (line.Contains("ERROR")) yield return line;}
Output
0, 1, 1, 2, 3, 5, 8, 13, 21, 34

This is how LINQ works internally: Where, Select and Take are iterators that pull items from the previous step one by one. A whole query processes one item at a time end-to-end instead of building intermediate lists.

Recursive data: walking a tree

Assembly.cs
public class Part{    public string Name { get; init; } = "";    public List<Part> Children { get; } = new();     public IEnumerable<Part> AllParts()      // depth-first, lazily    {        yield return this;        foreach (var child in Children)            foreach (var p in child.AllParts())                yield return p;    }}
Quick check

What happens when you call a method containing yield return?

Quick check

With Fibonacci().Take(5), how many Fibonacci numbers are computed?

Practice

Write an iterator static IEnumerable<int> Range(int from, int to, int step) that yields from from up to and including to (use yield break if step ≤ 0). Print Range(0, 20, 5) and the sum of Range(1, 100, 1).

Show the solution
Solution.cs
static IEnumerable<int> Range(int from, int to, int step){    if (step <= 0) yield break;    for (int i = from; i <= to; i += step)        yield return i;} Console.WriteLine(string.Join(" ", Range(0, 20, 5)));Console.WriteLine(Range(1, 100, 1).Sum());
Output
0 5 10 15 20
5050

Remember

  • A method with yield return is an iterator returning IEnumerable<T>.
  • It runs lazily, pausing at each yield return and resuming on the next request.
  • Lazy sequences handle huge or infinite data with little memory.
  • LINQ operators are iterators; enumerate twice = run twice.
0 of 60 lessons completed