Using C# Switch When Condition for Pattern Matching
c# switch when condition: Learn how to use the when clause in C# switch statements to apply additional conditions to case patterns, with practical examples and perform...
In C#, the switch statement has evolved from a simple value-based dispatcher into a powerful pattern-matching tool. One of the most useful features is the when clause, which lets you attach an additional condition to a case pattern. This article explains how to use the C# switch when condition effectively, covering syntax, practical examples, and performance considerations.
Understanding the when Clause in Switch Statements
The when clause in a C# switch statement acts as a guard that must evaluate to true for the corresponding case to match. It is placed after a pattern and uses the keyword when followed by a boolean expression. This allows you to filter patterns beyond the pattern itself, making the switch statement more expressive and reducing the need for nested if statements inside case blocks.
For example, consider a switch that handles different shapes. Without when, you can only match on the type. With when, you can also check properties of the matched object, such as whether a circle has a positive radius or a rectangle is a square.
Basic Syntax of switch with when
The syntax for a when clause is straightforward:
switch (value) { case Pattern when condition: // code to execute break; // other cases }
The condition is any boolean expression that can reference the pattern variable introduced by the pattern. For instance:
int number = 10; switch (number) { case int n when n > 5: Console.WriteLine($"{n} is greater than 5"); break; case int n when n <= 5: Console.WriteLine($"{n} is 5 or less"); break; }
Here, the when clause narrows the match for each case. The pattern variable n is available inside the condition and the case body. This is a simple example, but the same syntax applies to any pattern, including type patterns, property patterns, and relational patterns.
Practical Examples: Using when with Type Patterns
A common use case is combining type patterns with when to handle different subtypes conditionally. Consider a base class Shape with derived classes Circle and Rectangle. You can use a switch to compute area based on the specific type and additional conditions:
public abstract class Shape { } public class Circle : Shape { public double Radius { get; set; } } public class Rectangle : Shape { public double Width { get; set; } public double Height { get; set; } } public static double GetArea(Shape shape) { switch (shape) { case Circle c when c.Radius > 0: return Math.PI * c.Radius * c.Radius; case Rectangle r when r.Width > 0 && r.Height > 0: return r.Width * r.Height; default: return 0; } }
Without the when clause, you would need to check the radius or dimensions inside each case block and possibly throw an exception or return a default value. The when clause keeps the validation logic directly in the case pattern, making the intent clear and preventing invalid shapes from being processed.
Combining when with Relational and Logical Patterns
C# 9 and later support relational patterns and logical patterns, which can be combined with when for even more expressive conditions. Relational patterns use <, >, <=, >= to match numeric ranges. Logical patterns use and, or, and not to combine patterns.
For example, you can classify a temperature reading without a when clause using relational patterns:
int temperature = 25; switch (temperature) { case < 0: Console.WriteLine("Freezing"); break; case >= 0 and < 20: Console.WriteLine("Cold"); break; case >= 20 and < 30: Console.WriteLine("Warm"); break; default: Console.WriteLine("Hot"); break; }
However, there are cases where relational patterns alone are insufficient. For instance, if you need to match a pattern based on a property that is not directly part of the pattern, when becomes essential. Combining both can be powerful:
switch (shape) { case Circle c when c.Radius > 10 && c.Radius < 50: // medium circle break; case Circle c when c.Radius >= 50: // large circle break; }
Here, the when clause adds a range check that could also be expressed with relational patterns, but the property access inside when gives you flexibility to use any boolean expression, including method calls or complex conditions.
Common Mistakes and How to Avoid Them
One frequent mistake is forgetting that the when clause is evaluated only after the pattern matches. If the pattern itself does not match, the when condition is never evaluated. This is usually what you want, but it can lead to subtle bugs if you assume the pattern variable is always non-null. For example:
object obj = null; switch (obj) { case string s when s.Length > 0: // This case will not match because null does not match string pattern break; default: // Handles null break; }
Another mistake is placing the when clause on a case that uses a constant pattern. Constant patterns do not introduce a variable, so you cannot reference a pattern variable in the condition. For instance:
int value = 5; switch (value) { case 5 when value > 3: // This is valid because 'value' is in scope, but it's redundant break; }
While this compiles, it is often clearer to use an if statement or a relational pattern instead. The when clause is most useful when you need to access properties of a type pattern or when the condition depends on external state.
A third mistake is using when with a case that has no pattern (i.e., a default case). The default case cannot have a when clause; if you need conditional logic in the default, use an if statement inside it.
Performance and Runtime Considerations
The when clause does not introduce significant overhead. The runtime evaluates the pattern first, and if it matches, it evaluates the boolean condition. This is similar to an if statement inside the case block, but the compiler can sometimes optimize the pattern matching more efficiently. However, if the condition is expensive (e.g., a database call or a complex computation), it will run every time the pattern matches, just as it would in an if block. For performance-critical code, keep the condition cheap and avoid side effects.
One subtle performance aspect is that the order of cases matters. The switch statement evaluates cases in order, and the first case whose pattern matches and whose when condition is true is selected. This means you should order cases from most specific to least specific to avoid unnecessary evaluations. For example, place a case with a when condition that is likely to be true before a more general case.
Another consideration is that the C# compiler may convert a switch statement into a jump table or a series of comparisons depending on the patterns. When when clauses are present, the compiler often falls back to a sequential evaluation because the conditions are arbitrary. This is not a problem in most applications, but it is worth knowing if you are optimizing a hot path.
When to Use switch with when vs. if-else Chains
The when clause makes switch statements more powerful, but it does not replace if-else chains entirely. Use a switch with when when you are primarily dispatching on a type or a value and need to add a secondary condition. This keeps related logic together and is often more readable than a long if-else chain. Use if-else when the logic is not based on a single value or type, or when you have complex branching that does not fit a pattern.
For example, if you need to check multiple unrelated conditions, an if-else chain is clearer. But if you are handling different shapes or message types, a switch with when is more idiomatic and maintainable. The choice ultimately depends on the structure of your data and the conditions you need to evaluate.