switch Expressions
A switch expression produces a value from its first matching arm, with _ as the catch-all for everything left over.
result = value switch { pattern => expression, _ => fallback };A switch statement runs code. A switch expression produces a value. When every branch of a decision ends up assigning to the same variable, the expression form states that in one piece instead of four, and the variable can be given its value where it is declared.
It was added in C# 8.0. Its shape differs from the statement in three ways. The value comes first and the switch keyword follows it. Each branch is an arm: a pattern, then =>, then the value for that case, with arms separated by commas. There is no case and no break, and the closing brace is followed by a semicolon, because the whole construct is part of a statement rather than a statement itself.
Arms and the discard
Arms are tried from the top and the first match wins, so a specific arm has to sit above a general one. The compiler reports an error for an arm that an earlier arm already covers completely. _ is the discard pattern: it matches anything at all, including null, which is why it goes last and serves as the catch-all.
Exhaustiveness
Every possible input must match some arm. The compiler warns when it can see values that no arm handles, but a warning is not an error, and list patterns produce no warning at all. If nothing matches while the program runs, the result is a System.Runtime.CompilerServices.SwitchExpressionException. So either cover the input completely, as two arms do for a bool, or add a _ arm.
Example
int statusCode = 404;
// Statement form: a variable declared empty, then assigned in four places.
string statementForm;
switch (statusCode)
{
case 200:
statementForm = "ok";
break;
case 404:
statementForm = "not found";
break;
default:
statementForm = "other";
break;
}
// Expression form (C# 8.0): the same decision as a single value.
// The value comes first, then the switch keyword, then the arms.
string expressionForm = statusCode switch
{
200 => "ok",
404 => "not found",
_ => "other" // the discard pattern: matches everything left
};
Console.WriteLine(statementForm);
Console.WriteLine(expressionForm);
// Output:
// not found
// not foundWhen to use it
- Convert an enum value into the label shown in a user interface.
- Turn a numeric status code into a category used by a log line.
- Pick a retry delay from how many attempts have already failed.
- Map a short country code to a currency symbol in one assignment.
More examples
Exhaustive without a discard arm
Worth seeing once, because it shows what exhaustiveness actually means: the arms between them account for every value the input can hold. Leaving out the _ arm here is better than adding one, since a _ arm would be unreachable code that hides nothing.
bool isAdmin = false;
// bool has exactly two values, so these two arms cover every input.
// No _ arm is needed and the compiler gives no warning.
string role = isAdmin switch
{
true => "administrator",
false => "member"
};
Console.WriteLine(role);
// Output:
// memberor patterns and a when guard in the arms
Arms take the whole pattern language, so one arm can cover a set of constants, a range, or a range minus an exception. The 501 arm stays reachable because the arm above it is guarded, and a guarded arm never counts as covering everything its pattern describes.
int statusCode = 503;
string category = statusCode switch
{
// or groups several constants into one arm (C# 9.0). It is the
// expression form of stacking case labels in a switch statement.
400 or 404 or 409 => "client error",
// A relational pattern plus a when guard: both must hold.
>= 500 and < 600 when statusCode != 501 => "server error, retry later",
501 => "not implemented",
_ => "other"
};
Console.WriteLine(category);
// Output:
// server error, retry laterWhen the statement form is still the right tool
The expression form is not a replacement for the statement form. Use the expression when the branches differ only in the value they produce, and the statement when the branches differ in what they do. Forcing several actions into an arm is where switch expressions start to read badly.
int level = 2;
// Every arm of a switch expression must produce a value. When each
// branch does work instead, the statement form is what you want.
switch (level)
{
case 1:
Console.WriteLine("level 1");
Console.WriteLine("logging enabled");
break;
case 2:
Console.WriteLine("level 2");
Console.WriteLine("logging enabled");
Console.WriteLine("tracing enabled");
break;
default:
Console.WriteLine("logging disabled");
break;
}
// Output:
// level 2
// logging enabled
// tracing enabled
Discussion