Back to Blog
C#

C# Yield Break: How to End Iterator Methods

Learn how yield break terminates a C# iterator method, its behavior, and when to use it for early exit in lazy sequences.

yielditeratorC# programminglazy evaluationearly terminationIEnumerable
Diagram showing a C# iterator method with yield return and yield break, illustrating early termination of sequence generation.

yield break terminates a C# iterator method and signals that no further elements will be produced. It is the counterpart to yield return, which emits a single element. Use it when you need to stop enumeration early based on a runtime condition, such as reaching a sentinel value or a requested limit.

What yield break Does

An iterator method is any method that contains at least one yield statement and returns IEnumerable, IEnumerator, or their generic counterparts. When the compiler processes such a method, it generates a state machine that tracks the current position between calls to MoveNext().

yield break ends the iteration immediately. When the state machine encounters it, MoveNext() returns false, and the enumeration is considered complete. Normal code after yield break is not executed, and no exception is thrown; any active finally blocks still run. It is the explicit way to say "there are no more items."

Consider a simple example:

public IEnumerable<int> CountTo(int limit) { for (int i = 1; i <= limit; i++) { if (i == 3) yield break; yield return i; } }

Calling CountTo(5) and iterating over it yields 1 and 2, then stops. The loop would have continued to 5, but yield break terminates the method early.

yield break vs. return in Iterator Methods

A common misconception is that return can be used to exit an iterator method. In a normal method, return exits and optionally returns a value. In an iterator method, a return statement is not allowed, with or without a value. If you try to write:

public IEnumerable<int> Wrong() { yield return 1; return; // Compiler error: return is not allowed in an iterator; use yield break }

The compiler rejects it. The only valid ways to end an iterator method are:

  • Falling off the end of the method (implicit termination).
  • Using yield break (explicit termination).

yield break is the only explicit statement that ends an iterator without producing an element. It also lets you stop the entire enumeration from anywhere in the method, including inside nested loops.

Practical Example: Early Termination Based on a Condition

The most common use of yield break is to stop generating elements when a certain condition is met. For instance, you might have a method that yields values from a collection until a sentinel value is encountered:

public IEnumerable<string> ReadUntilStop(IEnumerable<string> source) { foreach (var item in source) { if (item == "STOP") yield break; yield return item; } }

Here, if the source contains "STOP", the enumeration stops immediately, and no further items are processed. This is more efficient than filtering the entire collection because it avoids iterating over the remaining elements.

Another scenario is when you need to yield from multiple sources but want to stop after a certain number of items:

public IEnumerable<int> TakeFirst(IEnumerable<int> source, int count) { int yielded = 0; foreach (var item in source) { if (yielded >= count) yield break; yield return item; yielded++; } }

This manually implements a "take" operation, and yield break ensures that once the desired count is reached, the source is not enumerated further.

Using yield break in Nested Loops and Complex Logic

Iterator methods often contain nested loops or conditional logic. yield break exits the entire iterator method, not just the current loop. This is important to remember when you have multiple loops and want to stop the whole enumeration from deep inside.

For example, consider a method that yields pairs of numbers until a product exceeds a threshold:

public IEnumerable<(int, int)> GeneratePairs(int maxA, int maxB, int maxProduct) { for (int a = 1; a <= maxA; a++) { for (int b = 1; b <= maxB; b++) { if (a * b > maxProduct) yield break; // stops both loops yield return (a, b); } } }

Without yield break, you would need to set flags or break out of each loop individually. yield break provides a clean, immediate exit from the entire method.

Performance and Memory Implications

Iterator methods are lazily evaluated. Each MoveNext() call executes code until the next yield return or yield break. This means that if you use yield break to stop early, you avoid the cost of generating elements that would never be consumed. This can have a significant impact when the source is expensive to produce or when the consumer stops iterating early.

For example, if you have an iterator that reads from a database or a network stream, using yield break when a condition is met can prevent unnecessary I/O operations. The same applies to CPU-bound computations: you don't compute values that are never needed.

However, be aware that the state machine generated by the compiler still allocates an object to hold the iterator's state. The allocation cost is paid regardless of whether you use yield break. The benefit comes from not executing the remaining code paths, not from reducing allocation.

Common Mistakes and Edge Cases

One subtlety is that yield break inside a try block with a finally block will still execute the finally block. This is important for resource cleanup. For example:

public IEnumerable<string> ReadLines(string path) { using (var reader = new StreamReader(path)) { string line; while ((line = reader.ReadLine()) != null) { if (line.StartsWith("END")) yield break; yield return line; } } }

When yield break is hit, the using statement's Dispose is called, and the file is closed properly. This is a common pattern for iterating over resources with early termination.

Another edge case is that yield break can appear in a method that contains no yield return; such a method is still an iterator and returns an empty sequence. The return type still has to be an iterator-compatible type such as IEnumerable<T> or IEnumerator<T>. A yield break in a method with an incompatible return type is a compile-time error.

yield break is also valid in methods that return IEnumerator or IEnumerator<T>, not just IEnumerable and IEnumerable<T>.

When to Avoid yield break

While yield break is useful, it is not always the best choice. If you are simply filtering a sequence, LINQ's Where or TakeWhile may be more expressive and avoid the need for a custom iterator. For example, TakeWhile stops when a predicate fails, which is similar to what you might implement with yield break.

var result = source.TakeWhile(item => item != "STOP");

This is often clearer than writing a custom iterator. Use yield break when you need to control the iteration logic in ways that LINQ operators cannot easily express, such as when you need to combine multiple sources, maintain state across iterations, or perform side effects that must stop at a specific point.

Another case to avoid is using yield break to signal an error condition. If you need to communicate an exceptional situation, throwing an exception is more appropriate than silently ending the sequence. The consumer of the iterator would otherwise see an empty or truncated sequence without knowing why.

Compatibility and Compiler Behavior

yield break has been part of C# since version 2.0, so it is available in all modern .NET environments. The behavior is consistent across .NET Framework, .NET Core, and .NET 5+. The compiler generates a state machine that implements the required iterator interfaces. The exact implementation may vary, but the semantics of yield break are stable.

A final note: yield break ends the iteration and allows any active finally blocks to run, but it does not call Dispose() on the enumerator object itself. If you are manually iterating and stop before the iterator reaches its natural end, call Dispose(); foreach does this automatically when you exit the loop.

C# yield break: Practical Usage and Code Examples | RYUSLOG DEV