C# Switch
A switch lets a C# program choose one path from several possible matches. It is often cleaner than a long if/else if chain when one value, shape, or pattern decides what should happen. Modern C# supports both traditional switch statements and compact switch expressions.
Overview: How Switch Works
A switch compares an input expression against a list of labels or patterns. In a traditional switch statement, C# runs the statements belonging to the first matching case. In a switch expression, C# evaluates to a value from the first matching arm. Both forms are ordered: when several patterns could match, the first matching one wins.
Older C# code often uses switch with simple constants such as strings, integers, chars, or enum values. That style is still useful for menus, command names, status codes, and fixed categories. Modern C# also uses pattern matching in switch: a case can test a type, a relational condition such as >= 90, a property shape, null, or a combination of patterns.
A traditional switch statement does not automatically fall through from one non-empty case to the next. C# requires each reachable case section to end with a control-flow statement such as break, return, throw, or goto case. This is different from C and JavaScript, where accidental fall-through is a common bug. C# still allows multiple labels to share one case body when they are stacked before the statements.
A default label in a statement, or the discard pattern _ in an expression, handles unmatched input. Including a fallback is usually wise unless the compiler can prove all possibilities are covered, such as every member of a small enum in some situations. With a switch expression, missing matches can cause a runtime SwitchExpressionException, so exhaustiveness matters more than it does in a statement that simply does nothing after no case matches.
Under the hood, the C# compiler lowers a switch into branching logic in Intermediate Language. For dense integer cases it may emit a jump table. For strings it may generate efficient equality checks, sometimes with hashing. For pattern matching it emits tests in source order, including type checks, null checks, relational comparisons, and guard conditions. The important practical rule is simple: write cases from most specific to most general, because earlier matches hide later ones.
Syntax
string command = "open";
switch (command)
{
case "open":
Console.WriteLine("Opening");
break;
case "save":
case "write":
Console.WriteLine("Saving");
break;
default:
Console.WriteLine("Unknown command");
break;
}
| Part | Meaning |
|---|---|
switch (command) |
Evaluates the input expression once and compares it to the cases. |
case "open": |
Runs when the input matches that constant or pattern. |
break |
Leaves the switch statement after the case body finishes. |
default |
Optional fallback when no other case matches. |
case "save": case "write": |
Multiple labels can share the same statements. |
A switch expression has a different shape. It returns a value, uses => arms, separates arms with commas, and usually ends with a discard fallback.
int score = 84;
string grade = score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
_ => "Review"
};
Examples
Matching Fixed Commands
using System;
class Program
{
static void Main()
{
string command = "save";
switch (command)
{
case "open":
Console.WriteLine("Opening the document.");
break;
case "save":
Console.WriteLine("Saving the document.");
break;
case "close":
Console.WriteLine("Closing the document.");
break;
default:
Console.WriteLine("Command not recognized.");
break;
}
Console.WriteLine("Menu handled.");
}
}
Output:
Saving the document.
Menu handled.
The value of command is "save", so only that case body runs. The break exits the switch, and execution continues after the closing brace. The default case is skipped because a matching case was found.
Grouping Cases
using System;
class Program
{
static void Main()
{
char accessLevel = 'E';
switch (accessLevel)
{
case 'A':
Console.WriteLine("Administrator access");
break;
case 'E':
case 'M':
Console.WriteLine("Employee access");
break;
case 'G':
Console.WriteLine("Guest access");
break;
default:
Console.WriteLine("Unknown access level");
break;
}
}
}
Output:
Employee access
The labels 'E' and 'M' share one case body. This is the correct way to group equivalent values in a C# switch statement. Because no statements appear between the two labels, there is no illegal fall-through.
Returning a Value with a Switch Expression
using System;
class Program
{
static void Main()
{
int score = 84;
string grade = score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "Needs review"
};
Console.WriteLine($"Score: {score}");
Console.WriteLine($"Grade: {grade}");
}
}
Output:
Score: 84
Grade: B
This example uses relational patterns. C# checks the arms from top to bottom. 84 does not match >= 90, but it does match >= 80, so the expression produces "B". Later arms are not evaluated.
Patterns and when Guards
using System;
class Program
{
static void Main()
{
decimal orderTotal = 125m;
bool isMember = true;
string shipping = orderTotal switch
{
>= 100m when isMember => "Free priority shipping",
>= 100m => "Free standard shipping",
>= 50m => "Reduced shipping",
_ => "Standard shipping"
};
Console.WriteLine(shipping);
}
}
Output:
Free priority shipping
The first arm has both a relational pattern and a when guard. The amount must be at least 100m, and isMember must be true. Guards are useful when a match depends on extra state that is not part of the switched value itself.
How Switch Works Step by Step
- C# evaluates the expression after
switchone time. - The compiler checks each
caselabel or expression arm for valid constants, patterns, and reachable order. - At runtime, cases are tested according to the compiled decision plan. For pattern switches, source order matters when patterns overlap.
- In a statement, the matching case section runs until it reaches
break,return,throw, or another explicit transfer. - In an expression, the matching arm produces a value whose type must fit the overall expression type.
- If no statement case matches, the program continues after the switch. If no expression arm matches, the expression throws at runtime unless a fallback arm exists.
The compiler also performs useful safety checks. It rejects duplicate constant cases, reports unreachable switch expression arms, and prevents accidental fall-through between non-empty statement sections. These checks make switch a strong choice when a fixed set of alternatives is clearer than many separate if statements.
Common Mistakes
Forgetting break in a Switch Statement
string status = "new";
switch (status)
{
case "new":
Console.WriteLine("Create ticket");
case "closed":
Console.WriteLine("Archive ticket");
break;
}
This does not compile. C# does not allow execution to fall from a non-empty case into the next case. Add break, return, or another explicit transfer after the first case body.
string status = "new";
switch (status)
{
case "new":
Console.WriteLine("Create ticket");
break;
case "closed":
Console.WriteLine("Archive ticket");
break;
}
Output:
Create ticket
Putting a General Pattern Before a Specific One
int score = 95;
string label = score switch
{
>= 60 => "Passing",
>= 90 => "Excellent",
_ => "Not passing"
};
This does not compile because the >= 90 arm can never be reached. Every value that is at least 90 already matches >= 60. Put the more specific pattern first.
int score = 95;
string label = score switch
{
>= 90 => "Excellent",
>= 60 => "Passing",
_ => "Not passing"
};
Console.WriteLine(label);
Output:
Excellent
Using Switch When If Is Clearer
int age = 20;
bool hasId = true;
if (age >= 18 && hasId)
{
Console.WriteLine("Entry allowed");
}
else
{
Console.WriteLine("Entry denied");
}
Output:
Entry allowed
This is not wrong, but it is intentionally shown as an if. When there are only two Boolean outcomes, especially with one combined condition, if is often easier to read than forcing the logic into a switch.
Best Practices
- Use a
switchstatement when each case performs actions. Use aswitchexpression when you need to compute one value. - Include
defaultor_unless you are deliberately allowing unmatched values and have tested that behavior. - Order overlapping patterns from most specific to most general.
- Group equivalent constant labels by stacking labels before one shared body.
- Prefer enums over magic strings when the set of possible values is controlled by your program.
- Keep case bodies short. Move large behavior into methods so the decision remains easy to scan.
- Use
whenguards for extra conditions, but avoid hiding complicated business rules inside one huge switch. - Do not use
goto casefor normal grouping. Stacked labels are clearer for shared behavior.
Practice Exercises
- Create a
switchstatement for astring rolewith cases for"admin","editor", and"viewer". Print a different permission message for each role. - Write a
switchexpression that converts anint monthinto"Winter","Spring","Summer", or"Fall". Use grouped patterns or logical patterns if you know them. - Build a grading switch expression for scores from
0to100. Add a fallback for impossible values such as-1or105.
Summary
switchchooses a path by matching one expression against cases or patterns.- A
switchstatement runs actions; aswitchexpression produces a value. - C# prevents accidental fall-through between non-empty case sections.
defaultand_provide fallback behavior for unmatched input.- Modern C# switches can use constants, relational patterns, type patterns, property patterns, and
whenguards. - Case order matters when patterns overlap, so put specific cases first.
- Use
switchwhen it improves clarity over a long chain ofif/else ifchecks.
