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.
Writing an iterator
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} ");0 6 12 18 24
The return type must be IEnumerable<T>(orIEnumerator<T>, orIAsyncEnumerable<T>).Calling the method runs none of its code; it returns an iterator object. Each MoveNext(eachforeachstep) runs until the nextyield return, then pauses with all local variables kept.yield break;ends the sequence early.
Infinite and huge sequences
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;}0, 1, 1, 2, 3, 5, 8, 13, 21, 34
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
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; }}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
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());0 5 10 15 20 5050
Remember
A method with yield returnis an iterator returningIEnumerable<T>.It runs lazily, pausing at each yield returnand resuming on the next request.Lazy sequences handle huge or infinite data with little memory. LINQ operators are iterators; enumerate twice = run twice.