When you need to branch on multiple possible values of a single expression, a switch is cleaner than a chain of if/else if statements. C# has two forms: the classic switch statement (similar to other languages) and the modern C# 8 switch expression which is more concise and enforces exhaustiveness. Switch expressions are widely used in ASP.NET Core source code — for mapping HTTP status codes, handling command types, and routing requests by type.
Classic Switch Statement
string dayName = "Monday";
switch (dayName)
{
case "Saturday":
case "Sunday":
Console.WriteLine("Weekend");
break; // required — prevents fall-through to next case
case "Monday":
case "Friday":
Console.WriteLine("Almost there");
break;
default: // executed when no case matches
Console.WriteLine("Weekday");
break;
}
// Output: Almost there
// Switch on an integer
int statusCode = 404;
switch (statusCode)
{
case 200:
Console.WriteLine("OK"); break;
case 201:
Console.WriteLine("Created"); break;
case 400:
Console.WriteLine("Bad Request"); break;
case 401:
case 403:
Console.WriteLine("Auth error"); break;
case 404:
Console.WriteLine("Not Found"); break;
default:
Console.WriteLine("Unknown"); break;
}
Note: Unlike C and Java, C# does not allow implicit fall-through between cases — you must always end each case with
break, return, throw, or goto case. This prevents one of the most common bugs in C-family languages where a forgotten break accidentally executes the next case. The compiler enforces this at compile time, not runtime. The only exception is empty cases that share a body (like case "Saturday": case "Sunday": stacked with no code between them).Tip: For switching on string values in C# 8+, prefer the switch expression (shown next) over the classic statement. Switch expressions are more concise, evaluated as a single expression (usable directly in assignments), and the compiler warns you if you miss a possible value when you use enums. They are increasingly the idiomatic choice in modern .NET code and appear throughout ASP.NET Core minimal API route handlers.
Warning: Switch statements in C# are case-sensitive for string values.
case "Monday" does not match "monday" or "MONDAY". If you are switching on user input or data from external sources, normalise the case first: switch (input.ToLower()) or switch (input.Trim().ToLowerInvariant()). In API controllers, model binding typically normalises input before it reaches your switch logic, but be explicit when processing raw strings.Switch Expression (C# 8+)
// Switch expression — evaluates to a value
int statusCode2 = 404;
string message = statusCode2 switch
{
200 => "OK",
201 => "Created",
400 => "Bad Request",
401 or 403 => "Auth Error", // 'or' pattern — matches either value
404 => "Not Found",
>= 500 => "Server Error", // relational pattern
_ => "Unknown" // _ is the discard / default arm
};
Console.WriteLine(message); // "Not Found"
// Switch expression on enum — compiler warns on missing members
enum Direction { North, South, East, West }
Direction dir = Direction.North;
string arrow = dir switch
{
Direction.North => "↑",
Direction.South => "↓",
Direction.East => "→",
Direction.West => "←",
// No _ needed — all enum values covered (compiler verified)
};
// Switch expression with when guard clause
int score = 73;
string grade = score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 and < 80 => "C", // 'and' combines patterns
>= 60 => "D",
_ when score < 0 => "Invalid", // when guard adds extra condition
_ => "F"
};
Console.WriteLine(grade); // "C"
// Type pattern — switch on the runtime type of an object
object shape = new Circle(radius: 5.0);
double area = shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
_ => throw new ArgumentException("Unknown shape")
};
record Circle(double Radius);
record Rectangle(double Width, double Height);
Common Mistakes
Mistake 1 — Forgetting break in a classic switch (compile error)
❌ Wrong — compile error “Control cannot fall through from one case label to another”:
switch (x)
{
case 1:
DoA();
// missing break! compile error in C#
case 2:
DoB();
break;
}
✅ Correct — always end each case with break, return, or throw.
Mistake 2 — Non-exhaustive switch expression (runtime exception)
❌ Wrong — throws SwitchExpressionException if none of the arms match:
string result = statusCode switch { 200 => "OK", 404 => "Not Found" };
// If statusCode is 500: SwitchExpressionException at runtime!
✅ Correct — always include _ => defaultValue or _ => throw new ... as the final arm.