Iterators & yield return

10 questions found

What does 'yield return' do inside a method, and how does it turn an ordinary method into an iterator?

Beginner
'yield return' produces one value at a time from a sequence WITHOUT computing and storing the entire sequence upfront — the method's return type must be IEnumerable<T> (or IEnumerator<T>), and the compiler transforms it into a state machine that pauses execution at each yield and resumes from there on the next iteration request.
public IEnumerable<int> CountToThree() {
  yield return 1;
  yield return 2;
  yield return 3;
}
foreach (int n in CountToThree()) {
  Console.WriteLine(n); // 1, 2, 3
}
Real-world example Lazily generating a sequence of values one at a time instead of building and returning a fully materialized List<T>.

Common follow-ups: What does the method's execution state look like BETWEEN successive foreach iterations?

Fundamentals

Why is a method using 'yield return' considered LAZY, and what does that mean for when its code actually executes?

Beginner
Code inside an iterator method DOESN'T run at all when you first call it — it only executes incrementally, ONE STEP AT A TIME, as the consumer actually requests each next value (via foreach or manual MoveNext() calls) — meaning side effects (like Console.WriteLine) inside the method are deferred until iteration actually happens.
public IEnumerable<int> GetNumbers() {
  Console.WriteLine("Starting!"); // doesn't print until iteration actually begins
  yield return 1;
  yield return 2;
}
var numbers = GetNumbers(); // 'Starting!' NOT printed yet
foreach (var n in numbers) { } // NOW 'Starting!' prints, right before yielding 1
Real-world example Understanding why a database query wrapped in a yield-based method doesn't actually execute until you start enumerating its results.

Common follow-ups: What happens if you call GetEnumerator() twice on the SAME IEnumerable<T> returned from a yield-based method?

Collections

How would you implement a custom IEnumerable<T> on your own class using yield return, letting it support foreach?

Intermediate
Implement IEnumerable<T> on your class and provide a GetEnumerator() method (using 'yield return' internally) that produces the sequence of elements — this lets instances of your custom class be used directly in a foreach loop, just like a built-in collection.
public class WeekDays : IEnumerable<string> {
  private readonly string[] _days = { "Mon", "Tue", "Wed", "Thu", "Fri" };
  public IEnumerator<string> GetEnumerator() {
    foreach (var day in _days) yield return day;
  }
  IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
foreach (var day in new WeekDays()) Console.WriteLine(day);
Real-world example Making a custom domain collection class (like a Schedule or Playlist) directly usable in foreach loops, LINQ, and collection initializers.

Common follow-ups: Why must you implement BOTH the generic IEnumerable<T>.GetEnumerator() AND the non-generic IEnumerable.GetEnumerator()?

Interfaces & Abstract Classes

How does 'yield break' work, and how does it differ from a plain 'return' statement inside a regular method?

Intermediate
'yield break' immediately stops the iterator's sequence, signaling there are no more elements — it's the iterator equivalent of 'return' in a regular method, but specifically for terminating a lazy sequence early rather than producing a single return value.
public IEnumerable<int> TakeWhilePositive(IEnumerable<int> source) {
  foreach (int n in source) {
    if (n < 0) yield break; // stop the sequence entirely once a negative number is found
    yield return n;
  }
}

var result = TakeWhilePositive(new[] { 1, 2, -1, 3 }); // yields 1, 2, then stops
Real-world example Building a custom lazy filtering/limiting operator similar to LINQ's TakeWhile(), stopping the sequence early based on a condition.

Common follow-ups: Can you combine 'yield break' with a try/finally block to ensure cleanup runs even when the sequence stops early?

Exception Handling

How does an iterator method interact with try/finally, and when does the finally block actually execute given the method's lazy, on-demand nature?

Intermediate
A finally block inside an iterator runs when iteration COMPLETES NORMALLY, when 'yield break' is hit, OR when the consumer disposes the enumerator early (like breaking out of a foreach loop, which implicitly calls Dispose()) — ensuring cleanup code (like closing a resource) reliably runs regardless of how iteration actually ends.
public IEnumerable<string> ReadLinesLazily(string path) {
  using var reader = new StreamReader(path);
  string? line;
  while ((line = reader.ReadLine()) != null) {
    yield return line;
  }
  // the 'using' reader is disposed correctly even if the consumer breaks out of foreach early
}
Real-world example Guaranteeing a file handle or database cursor is properly closed even if the consumer only reads a few lines before breaking out of the loop.

Common follow-ups: How does a 'using' statement inside an iterator method get compiled differently to support this deferred cleanup behavior?

File I/O & Streams

How would you implement a custom lazy 'Where' and 'Select' from scratch using yield return, mirroring how LINQ actually works internally?

Advanced
Write generic iterator methods that take a source IEnumerable<T> and a predicate/selector delegate, iterating the source lazily and yielding transformed/filtered results one at a time — this is conceptually exactly how System.Linq.Enumerable.Where() and .Select() are implemented, composing cleanly without ever materializing an intermediate collection.
public static IEnumerable<T> MyWhere<T>(this IEnumerable<T> source, Func<T, bool> predicate) {
  foreach (var item in source) {
    if (predicate(item)) yield return item;
  }
}
public static IEnumerable<TResult> MySelect<T, TResult>(this IEnumerable<T> source, Func<T, TResult> selector) {
  foreach (var item in source) yield return selector(item);
}
Real-world example Understanding LINQ's actual internal implementation strategy by reimplementing a simplified version of Where/Select yourself.

Common follow-ups: Why does chaining MyWhere().MySelect() still remain fully lazy, never materializing an intermediate collection between the two calls?

LINQ

How would you implement infinite lazy sequence generation using yield return, and why is this safe despite never 'finishing'?

Advanced
An iterator method can contain an infinite loop (like 'while (true)') that yields indefinitely — this is completely safe because the method only computes ONE value at a time, on demand, whenever the consumer calls MoveNext(); nothing forces the ENTIRE infinite sequence to be computed unless the consumer explicitly tries to enumerate it all (e.g., calling .ToList() without a Take() first, which WOULD hang forever).
public IEnumerable<int> Fibonacci() {
  int a = 0, b = 1;
  while (true) { // infinite, but safe due to lazy evaluation
    yield return a;
    (a, b) = (b, a + b);
  }
}
var firstTen = Fibonacci().Take(10).ToList(); // safely gets exactly 10 values
Real-world example Generating an infinite sequence (Fibonacci numbers, natural numbers, or a repeating pattern) that's only ever consumed a finite amount at a time.

Common follow-ups: What would happen if you accidentally called .ToList() directly on this infinite Fibonacci() sequence without a preceding Take()?

LINQ

How does the compiler-generated state machine for an iterator method actually work internally, and what class does it implement?

Advanced
The compiler transforms an iterator method's body into a hidden, compiler-generated class implementing IEnumerable<T>/IEnumerator<T>, with a numeric 'state' field tracking exactly which yield point execution last paused at — calling MoveNext() resumes execution from that saved state, runs until the next yield (or the method's end), saves the new state, and returns whether a new value was produced.
// Conceptual illustration of the compiler-generated state machine:
// class <CountToThree>d__0 : IEnumerable<int>, IEnumerator<int> {
//   int state;
//   int current;
//   bool MoveNext() { switch (state) { case 0: current = 1; state = 1; return true; case 1: ... } }
// }
Real-world example Understanding what actually happens 'under the hood' when debugging or reasoning about an iterator method's performance characteristics.

Common follow-ups: Why does each separate call to GetEnumerator() on the SAME iterator method produce an entirely INDEPENDENT state machine instance?

Fundamentals

How would you combine yield return with async/await to build an IAsyncEnumerable<T>-producing async iterator method?

Advanced
Mark the method 'async' AND have it return IAsyncEnumerable<T>, then you can use 'await' for asynchronous work AND 'yield return' to produce elements within the SAME method — the consumer then uses 'await foreach' to consume the resulting asynchronous stream, awaiting each element as it becomes available.
public async IAsyncEnumerable<string> FetchPagesAsync(string url) {
  string? next = url;
  while (next != null) {
    var response = await httpClient.GetAsync(next); // async work
    var page = await response.Content.ReadAsStringAsync();
    yield return page; // yields a value
    next = ExtractNextUrl(page);
  }
}
Real-world example Streaming paginated API results one page at a time, combining asynchronous HTTP calls with lazy, incremental enumeration.

Common follow-ups: How would you add cancellation support to an async iterator method using the [EnumeratorCancellation] attribute?

Asynchronous Programming

How would you write a custom iterator that safely handles resource cleanup for a lazily-enumerated database cursor, ensuring the connection is closed even if enumeration is abandoned early?

Advanced
Wrap the resource acquisition in a 'using' statement (or try/finally) directly within the iterator method body — the compiler-generated state machine correctly ensures this cleanup logic runs whenever the enumerator is disposed, which happens automatically at the end of a normal foreach OR when a foreach loop is exited early via break/return/exception.
public IEnumerable<Row> QueryRows(string sql) {
  using var connection = new SqlConnection(connectionString);
  connection.Open();
  using var command = new SqlCommand(sql, connection);
  using var reader = command.ExecuteReader();
  while (reader.Read()) {
    yield return MapRow(reader);
  } // connection, command, and reader are all safely disposed, even if the consumer breaks out early
}
Real-world example Ensuring a database connection is reliably closed even when a consumer only needs the first few rows and breaks out of the enumeration early.

Common follow-ups: What subtle bug can occur if resource acquisition happens BEFORE the first yield, versus lazily on the first MoveNext() call?

File I/O & Streams