switch Statements
switch names a value once and lists the cases to match it against; every section must end by leaving, so nothing falls through.
switch (value) { case constant: /* ... */ break; default: /* ... */ break; }When one value is compared against many fixed possibilities, a chain of else if repeats that value on every line. The switch statement names it once, then lists what to match it against.
Each case label gives something to match. The statements after the matching label run. default is the section that runs when no label matched, and if there is no default and nothing matches, the switch does nothing at all. Labels are tried in the order they are written.
Nothing falls through by accident
Every switch section must end by leaving the switch, and break is the usual way to do that. Running off the end of one section into the next is a compiler error. A forgotten break therefore fails the build instead of quietly running the next case's code as well. (return and throw, both covered later in the course, also end a section.)
Grouping cases deliberately
Several labels can sit one above the other in front of a single section, and any of them runs it. That is how you say that a set of values is handled the same way. When you genuinely want one section to continue into another, goto case "free"; states it in the code, where a reader can see it.
What trips people up
A case label must be a constant: a literal, a const, or null. case limit: where limit is an ordinary variable does not compile. For a test rather than a fixed value, a label can carry a pattern and a when guard, as in case int n when n > 100:. String labels match exactly, including letter case, so "SAT" does not match "sat".
Example
string day = "sat";
// Longhand: the same variable compared again on every line.
if (day == "sat" || day == "sun")
{
Console.WriteLine("weekend");
}
else if (day == "mon")
{
Console.WriteLine("start of the week");
}
else
{
Console.WriteLine("ordinary weekday");
}
// The switch names the value once, then lists the labels.
switch (day)
{
// Two labels, one section: either value runs the same code.
case "sat":
case "sun":
Console.WriteLine("weekend");
break;
case "mon":
Console.WriteLine("start of the week");
break;
// Runs when no label above matched.
default:
Console.WriteLine("ordinary weekday");
break;
}
// Output:
// weekend
// weekendWhen to use it
- Turn a one-word command typed by a user into the matching action.
- Map an HTTP status code to a short message for a log line.
- Handle Saturday and Sunday with one section and the rest with another.
- Dispatch on a file extension to pick how a file is parsed.
More examples
A case that tests instead of matching
A when guard adds a condition that must hold on top of the pattern, and here that is what separates the two sections. A range does not actually need a guard, because a case label can carry a relational pattern by itself, as in case > 1000:; reach for when only for a condition a pattern cannot express, such as one that involves another variable. Order matters here in a way it does not with plain constants: swap the two sections and every amount over 1000 would be reported as medium, because the first matching label wins.
int amount = 250;
switch (amount)
{
// int n names the value, and when adds a condition that must
// also hold. Labels are still tried from the top down.
case int n when n > 1000:
Console.WriteLine("large");
break;
case int n when n > 100:
Console.WriteLine("medium");
break;
default:
Console.WriteLine("small");
break;
}
// Output:
// mediumDeliberate fall-through with goto case
Use this when one case is another case plus something extra: a trial is a free plan with a time limit. Because C# will not let a section fall through silently, the jump has to be written out, which means a reader can see it was intended rather than forgotten.
string plan = "trial";
switch (plan)
{
case "trial":
Console.WriteLine("14 days");
goto case "free"; // continue into the free section on purpose
case "free":
Console.WriteLine("1 project");
break;
default:
Console.WriteLine("unlimited projects");
break;
}
// Output:
// 14 days
// 1 projectConstant labels, and what is not one
This is the rule people hit first. A named const reads better than a bare 3 and is still a constant, so it is allowed; a variable holding the same number is not, because the compiler builds the switch from values it knows while compiling. When the limit really is a variable, use an if chain or a when guard.
const int FreeLimit = 3;
int projects = 3;
switch (projects)
{
// A const is allowed as a label: its value is fixed at build time.
case FreeLimit:
Console.WriteLine("at the free limit");
break;
case 0:
Console.WriteLine("no projects yet");
break;
default:
Console.WriteLine("other");
break;
}
// An ordinary variable cannot be a label:
// int limit = 3;
// case limit: // error: a constant value is expected
// Output:
// at the free limit
Discussion