# Labeled break and continue in C# 15: When and Why to Use the New Loop Labels
C# 15 introduces the long-awaited but controversial feature—labeled break and continue. Despite criticism from parts of the community, this addition solves specific problems that arise when working with nested loops and switch expressions. It's especially relevant for cases where you need to exit not the innermost block, but an outer loop without using goto or flags.
The Problem of Exiting Nested Constructs
Traditionally, C# lacked the ability to explicitly specify which loop to exit when using break or continue statements. This led to the need to introduce additional logical flags, split code into methods, or use goto—which goes against clean code principles. For example, when processing a two-dimensional data structure, it's often necessary to immediately stop all iterations upon meeting a certain condition:
string foundValue = null;
bool shouldBreak = false;
for (int x = 0; x < xMax; x++)
{
for (int y = 0; y < yMax; y++)
{
foundValue = GetValue(x, y);
if (foundValue == targetValue)
{
shouldBreak = true;
break;
}
}
if (shouldBreak)
{
break;
}
}
ProcessValue(foundValue);
This approach not only clutters the code but also creates a risk of errors—for instance, calling ProcessValue even if the value wasn't found. The alternative of extracting the logic into a separate method improves readability but isn't always applicable, especially if the loop state depends on multiple external variables.
Real-World Use Cases for Labeled break/continue
The most compelling case for using labels is working with switch inside a loop. Without labeled break, it's impossible to exit the outer loop from within a case branch. Previously, developers had to use goto or wrap the logic in lambdas/local functions. Now the solution is straightforward:
outer: foreach (var item in collection)
{
switch (item.Type)
{
case ItemType.StopAll:
break outer;
case ItemType.SkipGroup:
continue outer;
}
}
Such code clearly expresses the intent: break outer terminates the entire loop, while continue outer skips to the next iteration of the outer loop. This is especially useful in parsers, finite state machines, and event routing systems.
When to Avoid Labels
Despite technical correctness, labeled break and continue shouldn't be used everywhere. Their use is justified only in situations where alternatives worsen readability or performance. In most cases, prefer:
- Extracting logic into separate methods with early returns (early return).
- Using LINQ operators (FirstOrDefault, Any, etc.) for finding values.
- Using flags only with simple nesting structures.
Labels are justified when:
- The loop contains a switch with many cases that require flow control at the outer loop level.
- Performance is critical, and extracting to a method is undesirable due to overhead.
- The logic can't be decomposed without losing semantic integrity.
Comparing Approaches: Labels vs. Refactoring
Let's consider three ways to implement the same scenario—searching for a value in a matrix with the ability to skip rows and early termination:
Approach 1: Flags
— Pros: Compatible with all C# versions.
— Cons: High cognitive complexity, risk of errors.
Approach 2: Splitting into Methods
— Pros: Readability, testability, adherence to SOLID principles.
— Cons: Possible increase in call stack depth.
Approach 3: Labeled break/continue
— Pros: Compactness, precise expression of intent.
— Cons: Limited applicability, unfamiliarity to some developers.
The choice depends on context. For business logic, prefer approach 2. For system code or parsers—approach 3.
Key Points
- Labeled break and continue in C# 15 solve a narrow but real problem: flow control from nested switches or loops.
- Don't use labels instead of refactoring: if the logic is complex, better to extract it into a separate method.
- Classic use case—processing a collection via foreach with switch, where some cases should terminate or skip iterations of the outer loop.
- The feature is in Open status, but already accepted into the C# language group's Working Set.
- Avoid comparing non-equivalent examples: old and new code should solve the same task.
— Editorial Team
No comments yet.