1. Intro
You already know how to write code like this:
switch (color)
{
case "red":
Console.WriteLine("Stop!");
break;
case "yellow":
Console.WriteLine("Wait...");
break;
case "green":
Console.WriteLine("You can go!");
break;
default:
Console.WriteLine("What color is that?");
break;
}
But what if we want to assign a variable a different value depending on the input parameter, like return a string based on a number from a function? Before switch-expressions showed up, you had to write stuff like this:
string meaning;
switch (number)
{
case 42:
meaning = "The answer to the ultimate question of life, the universe, and everything";
break;
case 7:
meaning = "Lucky number";
break;
default:
meaning = "Just a regular number";
break;
}
Not exactly elegant for a 21st-century language, right? You want to just return a value right away, like in functional languages.
That's where switch-expressions come in — one of the coolest (and freshest) features in C#, starting from version 8.0.
2. switch-expressions: syntax you wanna hug
Let's compare: statement vs expression
The classic switch is a statement, but a switch-expression is an expression, meaning it evaluates to a value and you can assign it to a variable or return it from a method right away.
Structure of a switch-expression:
var result = value switch
{
pattern1 => expression1,
pattern2 => expression2,
_ => defaultExpression // "underscore" means "otherwise"
};
For example:
int number = 42;
string meaning = number switch
{
42 => "The answer to the ultimate question of life, the universe, and everything",
7 => "Lucky number",
_ => "Just a regular number"
};
Console.WriteLine(meaning); // => The answer to the ultimate question of life, the universe, and everything
It's concise, readable, no code spaghetti, not a single break;.
Heads up: the underscore _ means "catch all other cases", just like default.
3. switch-expressions and patterns: how they work together
This is where the real fun starts. In switch-expressions you can use not just regular values, but patterns: checks by range, type, object properties, deconstruction (breaking an object into its parts), and even combinatorial patterns (and, or, not).
Pattern examples in switch-expressions
Constant patterns
That's what we did above: comparing with 42, 7, and so on.
Ranges and comparisons
int age = 17;
string category = age switch
{
< 18 => "Minor",
>= 18 and <= 65 => "Adult",
> 65 => "Senior",
_ => "Unknown age category"
};
Console.WriteLine(category);
By the way, notice — you can use multiple conditions together! C# is getting more and more expressive.
Type patterns
Say, we have an object that can be of different types (like object value;). We can handle different types like this:
object value = 3.14;
string description = value switch
{
int i => $"Integer: {i}",
double d => $"Floating point: {d:F2}",
string s => $"This is a string: '{s}'",
null => "null!",
_ => "Something else"
};
Console.WriteLine(description); // => Floating point: 3.14
Property Patterns and Positional Patterns
With C# 8 and later, you can check property values right in a switch-expression.
Example with an object
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
User user = new User { Name = "Anna", Age = 17 };
string welcome = user switch
{
{ Age: < 18 } => $"Hello, {user.Name}! You're a teenager.",
{ Age: >= 18 and <= 65 } => $"Hi, {user.Name}! Adult life is awesome.",
{ Age: > 65 } => $"Welcome, {user.Name}! You're always welcome here.",
_ => "Unknown age"
};
Console.WriteLine(welcome);
Here in curly braces { Age: < 18 } — that's a property pattern, so we're checking not the whole object, but just its property.
4. New patterns in C# 11-14
C# is all about being concise and flexible. Recently, it got combinatorial patterns: and, or, not, and improved positional patterns. Let's break them down.
Logical patterns: and, or, not
You can build complex conditions, just like in math logic!
int temperature = 25;
string result = temperature switch
{
< 0 => "Freezing!",
>= 0 and < 20 => "Chilly",
>= 20 and < 30 => "Comfortable",
>= 30 or <= -30 => "Extreme!",
_ => "Weird temperature"
};
If you wanna exclude cases, go for it:
int score = 80;
string grade = score switch
{
>= 90 => "Excellent",
>= 60 and not >= 90 => "Good",
_ => "Bad"
};
Deconstruction (Positional Patterns)
If you have a struct or a record that supports deconstruction, C# lets you break the object apart right in the switch-expression:
public record Point(int X, int Y);
Point p = new Point(10, 20);
string desc = p switch
{
(0, 0) => "Origin",
(var x, 0) => $"On X axis, X={x}",
(0, var y) => $"On Y axis, Y={y}",
(var x, var y) when x == y => $"On the diagonal: {x}",
_ => $"At point ({p.X}, {p.Y})"
};
Console.WriteLine(desc); // => At point (10, 20)
5. When and why to use switch-expressions
In modern C#, switch-expressions aren't just a trendy "feature" — they're a real way to avoid boilerplate, make your code more compact, easier to test and maintain. This is especially critical when writing parsers, command handlers, business logic, config rules — anywhere you need to branch execution by complex criteria, not just by values, but also by types, ranges, properties, combos.
At interviews
- How would you handle different user responses?
- How do you return different function values depending on both type and value?
- How would you easily add new rules without a ton of if-else?
Show a switch-expression with patterns! That instantly shows you know the new language features and have good coding style.
Table: comparing switch statement and switch-expression
| Characteristic | switch statement | switch expression |
|---|---|---|
| Type | statement | expression |
| Use of break | required | not needed |
| Can return a value | only via a variable | yep, returns value directly |
| Can use patterns | partially | fully (type, property, etc.) |
| Gotcha | can forget break | all paths must return a value |
6. Typical mistakes and little gotchas
- Beginner devs often get confused between the switch statement and the switch-expression. For example, they forget that in a switch-expression you don't need break;, and every branch must return a value.
- If you forget the _ branch, the compiler will nudge you: "what if nothing matches?"
- You can't use goto in a switch-expression (thank goodness!).
- If you duplicate a condition (like two identical patterns), the compiler won't let that slide either.
- Be careful with the type of value returned from a switch-expression: it has to be compatible with the variable type you're assigning it to.
GO TO FULL VERSION