C# switch expression
last modified November 6, 2026
In this article we show how to use switch expressions in C#.
Switch expressions are a concise enhancement of the classic
switch statements, introduced in C# 8. A switch expression consists of
arms, each of which consists of a pattern, an optional when guard,
the => token, and an expression. The value of the first arm
whose pattern matches becomes the value of the whole switch expression.
The arms may evaluate the following:
- values
- types
- relational logic
- when guards
- properties
- positional patterns
- tuples
- lists
The arms are evaluated from top to bottom. The discard pattern
_ matches any value; it is typically used as the last arm.
C# switch expression value pattern
With a value pattern, also called a constant pattern, the switch arms are based on constant values such as integers or strings.
string[] domains = ["us", "DE", " sk ", "hu", "jp"];
foreach (var domain in domains)
{
Console.WriteLine($"{domain.Trim().ToLower()} -> {GetCountry(domain)}");
}
string GetCountry(string domain) => domain.Trim().ToLower() switch
{
"us" => "United States",
"de" => "Germany",
"sk" => "Slovakia",
"hu" => "Hungary",
_ => "Unknown"
};
In the example, we use a switch expression to map a domain name to its country name.
string GetCountry(string domain) => domain.Trim().ToLower() switch
The value to be matched comes before the switch keyword. The
switch expression is often the body of an expression-bodied function.
_ => "Unknown"
The discard arm is used for all domains that are not listed.
$ dotnet run us -> United States de -> Germany sk -> Slovakia hu -> Hungary jp -> Unknown
C# switch statement vs switch expression
The switch statement executes statements, while the switch expression produces a value.
int day = 3;
string name;
switch (day)
{
case 1:
name = "Monday";
break;
case 2:
name = "Tuesday";
break;
case 3:
name = "Wednesday";
break;
default:
name = "Unknown";
break;
}
Console.WriteLine(name);
string name2 = day switch
{
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
_ => "Unknown"
};
Console.WriteLine(name2);
The program converts a day number to a day name, first with a switch
statement and then with a switch expression. The switch expression does not
need the case and break keywords, and the result is
assigned directly to a variable.
$ dotnet run Wednesday Wednesday
C# switch expression type pattern
Data types can be patterns of the switch expression. A type pattern can also declare a variable which holds the converted value.
int age = 23;
string name = "Peter";
List<string> colors = ["blue", "khaki", "orange"];
int[] nums = [1, 2, 3, 4, 5];
double? weight = null;
Console.WriteLine(Check(age));
Console.WriteLine(Check(name));
Console.WriteLine(Check(colors));
Console.WriteLine(Check(nums));
Console.WriteLine(Check(3.5));
Console.WriteLine(Check(weight));
string Check(object? val) => val switch
{
int => "integer",
string s => $"string of length {s.Length}",
List<string> l => $"list of {l.Count} strings",
Array a => $"array of {a.Length} elements",
null => "null value",
_ => $"unknown type {val.GetType().Name}"
};
In the example, we find out the data type of a variable using a switch expression.
string s => $"string of length {s.Length}",
If the value is a string, it is assigned to the s variable of
type string, which is then used in the arm's expression.
null => "null value",
The null constant pattern matches a null reference. A type
pattern never matches null. The nullable double
without a value is boxed into a null reference.
$ dotnet run integer string of length 5 list of 3 strings array of 5 elements unknown type Double null value
C# switch expression relational pattern
Powerful logic can be constructed with relational patterns. They use the
<, >, <=, and
>= operators with constants.
List<int> nums = [-3, 2, 0, 1, 9, -2, 7];
foreach (var num in nums)
{
var res = num switch
{
< 0 => "negative",
0 => "zero",
> 0 => "positive"
};
Console.WriteLine($"{num} is {res}");
}
We have a list of integers. In a foreach loop, we go through the
list and print whether the value is negative, positive, or zero with a switch
expression. Inside the switch expression, we use simple relational patterns.
The three arms cover all integers, so no discard arm is needed.
$ dotnet run -3 is negative 2 is positive 0 is zero 1 is positive 9 is positive -2 is negative 7 is positive
C# switch expression when guards
Using when guards adds additional flexibility to our expression
arms. The guard is a boolean expression which must be true for the arm to be
selected.
List<User> users =
[
new("John", "Doe", 34),
new("Roger", "Roe", 44),
new("Jane", "Doe", 44),
new("Robert", "Molnar", 41),
new("Lucia", "Petrovicova", 16),
];
foreach (var user in users)
{
Console.WriteLine(CheckPerson(user));
}
string CheckPerson(User u) => u switch
{
{ LastName: "Doe" } => "Doe family",
{ Age: var age } when age < 18 => "minor person",
{ FirstName: var fname, Age: var age } when fname.StartsWith('R') && age > 40
=> $"{fname} is a senior R-member",
_ => $"{u}"
};
record User(string FirstName, string LastName, int Age);
In the example, we use a when guard to account for users younger
than 18.
{ Age: var age } when age < 18 => "minor person",
The var pattern captures the age into a variable, which is then
checked in the guard.
{ FirstName: var fname, Age: var age } when fname.StartsWith('R') && age > 40
=> $"{fname} is a senior R-member",
A guard can contain any boolean expression, including method calls.
$ dotnet run Doe family Roger is a senior R-member Doe family Robert is a senior R-member minor person
C# switch expression logical pattern
The and, or, and not pattern combinators
can be used in switch expressions.
var today = new DateOnly(2026, 11, 6);
List<User> users =
[
new("Peter Novak", "driver", new DateOnly(2000, 12, 1)),
new("John Doe", "gardener", new DateOnly(1996, 2, 10)),
new("Roger Roe", "teacher", new DateOnly(1961, 5, 9)),
new("Lucia Smith", "student", new DateOnly(2010, 8, 18)),
new("Roman Green", "retired", new DateOnly(1945, 7, 21)),
];
foreach (var user in users)
{
int age = GetAge(user.Dob, today);
string res = age switch
{
< 18 => "minor",
>= 18 and < 65 => "adult",
>= 65 => "senior",
};
Console.WriteLine($"{user.Name} ({age}) is {res}");
}
static int GetAge(DateOnly dob, DateOnly today)
{
int age = today.Year - dob.Year;
return dob > today.AddYears(-age) ? age - 1 : age;
}
record User(string Name, string Occupation, DateOnly Dob);
The logical and pattern is used to assign the adult string to
users with age from 18 to 64. To keep the output stable, the age is computed
relative to a fixed date.
string res = age switch
{
< 18 => "minor",
>= 18 and < 65 => "adult",
>= 65 => "senior",
};
The ranges cover all integers without gaps, so the compiler knows that the switch expression is exhaustive.
$ dotnet run Peter Novak (25) is adult John Doe (30) is adult Roger Roe (65) is senior Lucia Smith (16) is minor Roman Green (81) is senior
C# switch expression or and not patterns
In the following example, we use or and not patterns.
int[] answers = [1, 2, 3, 5];
foreach (var ans in answers)
{
string res = ans switch
{
1 or 3 => "incorrect",
2 => "correct",
_ => "unknown option"
};
Console.WriteLine($"WWI started in option {ans}: {res}");
}
char[] chars = ['a', 'Z', '7', '?'];
foreach (var c in chars)
{
string kind = c switch
{
>= 'a' and <= 'z' or >= 'A' and <= 'Z' => "letter",
>= '0' and <= '9' => "digit",
not (' ' or '\t') => "other",
_ => "whitespace"
};
Console.WriteLine($"{c} is {kind}");
}
The example evaluates answers to a quiz question and classifies characters.
1 or 3 => "incorrect",
The arm matches if the value is either 1 or 3.
>= 'a' and <= 'z' or >= 'A' and <= 'Z' => "letter",
The and combinator has higher precedence than or.
The pattern matches lowercase and uppercase ASCII letters.
not (' ' or '\t') => "other",
The not pattern negates a pattern. Parentheses group patterns.
$ dotnet run WWI started in option 1: incorrect WWI started in option 2: correct WWI started in option 3: incorrect WWI started in option 5: unknown option a is letter Z is letter 7 is digit ? is other
C# switch expression property pattern
The values of object properties can be patterns in a switch expression.
List<Product> products =
[
new("Product A", 70m, 1, 10),
new("Product B", 50m, 3, 15),
new("Product C", 35m, 2, 20)
];
foreach (var product in products)
{
decimal sum = product switch
{
{ Quantity: 2 } =>
product.Price * product.Quantity * (1 - product.Discount / 100m),
_ => product.Price * product.Quantity,
};
Console.WriteLine($"The final sum for {product.Name} is {sum}");
}
record Product(string Name, decimal Price, int Quantity, int Discount);
In the example, we apply a discount to the price of a product if we have bought two items of the product. Twenty percent discount was applied to Product C.
$ dotnet run The final sum for Product A is 70 The final sum for Product B is 150 The final sum for Product C is 56.0
C# switch expression nested property pattern
Property patterns can be nested. Since C# 10, we can also use the extended property pattern, which accesses nested properties with a dot.
List<Order> orders =
[
new(1, new Customer("John", new Address("Bratislava", "SK"))),
new(2, new Customer("Paul", new Address("Vienna", "AT"))),
new(3, new Customer("Anna", new Address("Kosice", "SK"))),
new(4, new Customer("Lucy", new Address("Berlin", "DE"))),
];
foreach (var order in orders)
{
string shipping = order switch
{
{ Customer.Address.Country: "SK" } => "domestic",
{ Customer.Address: { Country: "AT" or "DE" } } => "EU neighbour",
_ => "international"
};
Console.WriteLine($"Order {order.Id} for {order.Customer.Name}: {shipping}");
}
record Address(string City, string Country);
record Customer(string Name, Address Address);
record Order(int Id, Customer Customer);
We determine the shipping type of orders based on the customer's country.
{ Customer.Address.Country: "SK" } => "domestic",
{ Customer.Address: { Country: "AT" or "DE" } } => "EU neighbour",
The first arm uses the extended property pattern, the second one nests a property pattern inside another. Both forms are equivalent; the extended pattern is more readable.
$ dotnet run Order 1 for John: domestic Order 2 for Paul: EU neighbour Order 3 for Anna: domestic Order 4 for Lucy: EU neighbour
C# switch expression positional pattern
A positional pattern deconstructs the value and matches its parts. Records
with primary constructors provide a Deconstruct method
automatically.
Point[] points = [new(0, 0), new(3, 0), new(0, -2), new(4, 4), new(2, 5)];
foreach (var p in points)
{
string pos = p switch
{
(0, 0) => "origin",
(_, 0) => "on the x-axis",
(0, _) => "on the y-axis",
var (x, y) when x == y => "on the diagonal",
var (x, y) => $"at {x}, {y}"
};
Console.WriteLine($"{p} is {pos}");
}
record Point(int X, int Y);
We determine the position of points in a plane.
(_, 0) => "on the x-axis",
The pattern matches points whose Y coordinate is zero. The
discard ignores the X coordinate.
var (x, y) when x == y => "on the diagonal",
The coordinates are deconstructed into variables and compared in a guard.
$ dotnet run
Point { X = 0, Y = 0 } is origin
Point { X = 3, Y = 0 } is on the x-axis
Point { X = 0, Y = -2 } is on the y-axis
Point { X = 4, Y = 4 } is on the diagonal
Point { X = 2, Y = 5 } is at 2, 5
C# switch expression tuple pattern
A tuple pattern matches several values at once.
string[] options = ["rock", "paper", "scissors"];
var rnd = new Random(17);
for (int i = 0; i < 5; i++)
{
string human = options[rnd.Next(options.Length)];
string computer = options[rnd.Next(options.Length)];
Console.WriteLine($"I have {computer}, you have {human}");
Console.WriteLine(RockPaperScissors(human, computer));
}
string RockPaperScissors(string human, string computer) => (human, computer) switch
{
("rock", "paper") => "Rock is covered by paper. You lose.",
("rock", "scissors") => "Rock breaks scissors. You win.",
("paper", "rock") => "Paper covers rock. You win.",
("paper", "scissors") => "Paper is cut by scissors. You lose.",
("scissors", "rock") => "Scissors are broken by rock. You lose.",
("scissors", "paper") => "Scissors cut paper. You win.",
_ => "Tie."
};
We play several rounds of the rock paper scissors game. The random generator has a fixed seed, so the output is the same on every run.
string RockPaperScissors(string human, string computer) => (human, computer) switch
{
("rock", "paper") => "Rock is covered by paper. You lose.",
("rock", "scissors") => "Rock breaks scissors. You win.",
From the human and computer choices we form tuples which are used as patterns in the switch expression to reach the game conclusion. The discard arm covers all the ties.
$ dotnet run I have scissors, you have paper Paper is cut by scissors. You lose. I have rock, you have paper Paper covers rock. You win. I have paper, you have scissors Scissors cut paper. You win. I have scissors, you have paper Paper is cut by scissors. You lose. I have paper, you have rock Rock is covered by paper. You lose.
C# switch expression list patterns
Since C# 11, switch expression arms can evaluate list patterns.
List<Student> students = [
new("John", ['a', 'b', 'a', 'c', 'd', 'd', 'e']),
new("Lucia", ['b', 'b', 'c', 'c', 'a', 'e', 'e']),
new("Paul", ['a', 'b', 'a', 'c', 'b', 'e', 'e']),
new("Roger", ['a', 'c', 'c', 'c', 'a', 'b', 'e']),
new("Michal", ['b', 'b', 'c', 'c', 'a', 'e', 'e']),
];
foreach (var student in students)
{
string res = student.Answers switch
{
['b', 'b', 'c', 'c', 'a', 'e', 'e'] => $"{student.Name} has passed",
_ => $"{student.Name} has failed"
};
Console.WriteLine(res);
}
record Student(string Name, List<char> Answers);
Students are having a test with seven questions. The questions have answers 'a' through 'e'. To pass a test, all questions must be answered correctly.
string res = student.Answers switch
{
['b', 'b', 'c', 'c', 'a', 'e', 'e'] => $"{student.Name} has passed",
_ => $"{student.Name} has failed"
};
We check results against the correct one. The pattern is a list of characters that represent correct answers.
$ dotnet run John has failed Lucia has passed Paul has failed Roger has failed Michal has passed
C# switch expression slice pattern
List patterns become more flexible with the slice pattern ..,
which matches zero or more elements.
int[][] data = [[], [7], [1, 2], [1, 5, 9], [4, 0, 0, 0, 8], [2, 3, 4, 5]];
foreach (var vals in data)
{
string desc = vals switch
{
[] => "empty",
[var x] => $"single value {x}",
[var first, var last] => $"pair {first} and {last}",
[1, ..] => "starts with one",
[var first, .. var mid, var last] when mid.All(e => e == 0)
=> $"{first} and {last} with {mid.Length} zeros between",
[_, .., var last] => $"ends with {last}"
};
Console.WriteLine($"[{string.Join(", ", vals)}]: {desc}");
}
We describe several arrays with list patterns.
[] => "empty",
[var x] => $"single value {x}",
The empty list pattern matches an empty array; [var x] matches an
array with exactly one element.
[1, ..] => "starts with one",
The slice pattern matches any number of remaining elements.
[var first, .. var mid, var last] when mid.All(e => e == 0)
A slice can be captured into a variable. For arrays, the captured slice is a new array.
$ dotnet run []: empty [7]: single value 7 [1, 2]: pair 1 and 2 [1, 5, 9]: starts with one [4, 0, 0, 0, 8]: 4 and 8 with 3 zeros between [2, 3, 4, 5]: ends with 5
C# switch expression exhaustiveness
The compiler checks whether the arms of a switch expression cover all
possible input values. If they do not, it issues the CS8509 warning. If no
arm matches at runtime, a SwitchExpressionException is thrown.
Console.WriteLine(GetName(2));
try
{
Console.WriteLine(GetName(5));
}
catch (System.Runtime.CompilerServices.SwitchExpressionException e)
{
Console.WriteLine(e.Message);
}
#pragma warning disable CS8509
string GetName(int day) => day switch
{
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday"
};
The switch expression handles only three values. We disable the warning to demonstrate the runtime behaviour; in real code, we add a discard arm or an arm that throws a more descriptive exception.
$ dotnet run Tuesday Non-exhaustive switch expression failed to match its input. Unmatched value was 5.
Source
switch expression - pattern matching expressions using the switch keyword
In this article we have presented C# switch expression.
Author
List all C# tutorials.