Switch-выражение сопоставляет входному значению результат. Оно появилось в C# 8 и заменяет частый шаблон, когда каждый case оператора switch просто что-то возвращает или присваивает:
// C# 8 and later
string StatusText(int code) => code switch
{
200 => "OK",
301 or 302 => "Redirect", // 'or' needs C# 9
404 => "Not Found",
>= 500 => "Server Error", // relational patterns need C# 9
_ => "Unknown"
};
Читайте его сверху вниз: значение перед switch проверяется по очереди на каждой ветви, и первая ветвь, чей шаблон совпал, даёт результат. Нет ни case, ни break, ни ключевого слова default; _ (шаблон отбрасывания) совпадает с чем угодно и служит запасным вариантом.
В C# 7 то же сопоставление записывается оператором switch в небольшом методе с return в каждом case:
Вывод:
200 OK
302 Redirect
404 Not Found
503 Server Error
418 Unknown
Форма выражения короче в основном потому, что каждая ветвь занимает одну строку, а тела метода больше нет. К тому же это выражение, поэтому оно может стоять везде, где допустимо значение: в аргументе, в интерполяционной вставке, в return, в инициализаторе поля.
Правила синтаксиса
var result = input switch
{
pattern1 => value1,
pattern2 when condition => value2,
_ => fallback
};
- Сначала идёт входное значение, затем
switch. Это обратный порядок по сравнению с оператором. - Каждая ветвь имеет вид
pattern => expression. Правая часть должна быть одним выражением; для более сложной логики вызовите метод. - Ветви разделяются запятыми. Запятая после последней ветви допускается.
- Вся конструкция это выражение, поэтому использующий её оператор заканчивается
;после закрывающей скобки. - Все ветви должны выдавать значения общего типа.
var x = n switch { 0 => "zero", _ => 0 };не компилируется (CS8506, no best type). Начиная с C# 9 это решает объявленный целевой тип:object x = n switch { 0 => "zero", _ => 0 };компилируется. - Каждая ветвь должна выдавать значение, поэтому ветвь не может вызвать метод
void, напримерConsole.WriteLine, а switch-выражение не может стоять отдельно как оператор (CS0201). Для действий используйте оператор switch. - Ветвь может выбросить исключение вместо значения:
_ => throw new ArgumentOutOfRangeException(nameof(input)). - Компилятор отвергает ветвь, которая никогда не может быть достигнута, потому что предыдущая уже её покрывает, с ошибкой CS8510,
The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match.Ставьте конкретные ветви перед общими.
Реляционные и логические шаблоны (C# 9)
C# 9 добавил <, <=, >, >= в качестве шаблонов и комбинаторы and, or и not. С ними таблицы диапазонов читаются как спецификация:
// C# 9 and later
string Grade(int score) => score switch
{
< 0 or > 100 => throw new ArgumentOutOfRangeException(nameof(score)),
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F"
};
string AgeGroup(int age) => age switch
{
< 13 => "child",
>= 13 and <= 19 => "teen",
_ => "adult"
};
bool IsLetter(char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';
Поскольку ветви проверяются по порядку, >= 80 видит только оценки ниже 90. Реляционные шаблоны принимают справа только константы; чтобы сравнить с переменной, используйте условие when. Эквивалент таблицы диапазонов на C# 7 это цепочка if:
Вывод:
95 -> A
83 -> B
71 -> C
64 -> D
12 -> F
not чаще всего используют как not null: x is not null или ветвь not null => x.Name.
Шаблоны свойств
Шаблон свойства сопоставляет члены объекта: { Property: pattern, ... }. Вместе со switch-выражением он выражает бизнес-правила без цепочки условий &&:
// C# 8 and later
decimal ShippingCost(Order order) => order switch
{
{ Total: >= 100m } => 0m, // relational inside: C# 9
{ Country: "US", Express: true } => 15m,
{ Country: "US" } => 5m,
{ Express: true } => 30m,
null => throw new ArgumentNullException(nameof(order)),
_ => 12m
};
Чтобы ветвь совпала, должны совпасть все перечисленные свойства. Пустой шаблон свойства { } совпадает с любым значением, отличным от null. C# 10 добавил расширенные шаблоны свойств для вложенных членов: { Customer.Address.Country: "US" } вместо { Customer: { Address: { Country: "US" } } }.
Шаблоны кортежей
Switch по кортежу сопоставляет несколько значений сразу, и это аккуратный способ выразить таблицу решений:
// C# 9 and later (the 'or' pattern)
string Winner(string a, string b) => (a, b) switch
{
("rock", "scissors") or ("scissors", "paper") or ("paper", "rock") => "player 1",
var (x, y) when x == y => "draw",
_ => "player 2"
};
var (x, y) деконструирует кортеж в две переменные, которые может сравнить условие when. В C# 7 обычная замена это объединить значения в один ключ или использовать словарь результатов:
Вывод:
player 1
player 2
draw
Шаблоны типов и when
Ветвь может проверить тип во время выполнения и связать переменную, а условие when добавляет любое условие, которое нельзя выразить синтаксисом шаблонов:
// C# 8 and later
double Area(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r when r.Width == r.Height => r.Width * r.Width,
Rectangle r => r.Width * r.Height,
null => throw new ArgumentNullException(nameof(shape)),
_ => throw new NotSupportedException(shape.GetType().Name)
};
Версия на C# 7 использует шаблон типа is в цепочке if, который связывает переменную точно так же:
Вывод:
Circle: 12.57
Rectangle: 13.50
Полнота и SwitchExpressionException
Switch-выражение должно выдавать значение для любого входа. Когда компилятор видит входы, которые не совпадают ни с одной ветвью, он предупреждает:
warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '0' is not covered.
Это предупреждение, а не ошибка, поэтому код всё равно собирается. Если несовпавшее значение приходит во время выполнения, выражение выбрасывает System.Runtime.CompilerServices.SwitchExpressionException, сообщение которого заканчивается несовпавшим значением (Unmatched value was 3.).
Для перечислений есть особый случай. Даже когда для каждого именованного члена есть ветвь, переменная перечисления может хранить любое целое число ((Size)3), поэтому компилятор сообщает CS8524 о «безымянном значении перечисления»:
// C# 8 and later
enum Size { Small, Medium, Large }
string Code(Size s) => s switch
{
Size.Small => "S",
Size.Medium => "M",
Size.Large => "L",
// warning CS8524 without the next arm
_ => throw new ArgumentOutOfRangeException(nameof(s))
};
Добавьте явную ветвь _, которая выбрасывает понятное исключение. Это документирует предположение, убирает предупреждение и даёт более ясную ошибку, чем SwitchExpressionException, если плохое значение когда-нибудь появится.
Словарь как альтернатива
Когда ветви это чистые пары «константа и значение», а таблица может расти или приходить из конфигурации, Dictionary делает ту же работу в любой версии C# и может строиться во время выполнения:
Вывод:
19.00
11.50
0
Неизвестная страна получает ставку 0m. У произведения decimal столько знаков после запятой, сколько у двух операндов вместе, поэтому 100m * 0.19m печатает 19.00, а нулевая ставка печатает 0.
Switch-выражение и оператор switch
| оператор switch | switch-выражение | |
|---|---|---|
| Доступно с | C# 1 | C# 8 |
| Выдаёт значение | нет | да |
| Синтаксис ветки | case X: ... break; | X => value, |
| Запасной вариант | default: | _ => |
| Несколько операторов в ветке | да | нет, вызовите метод |
| Предупреждение о полноте | нет | да (CS8509) |
| Нет совпадения во время выполнения | ничего не выполняется | SwitchExpressionException |
Обе формы принимают одни и те же шаблоны (типы, константы, свойства, кортежи, реляционные), поэтому, изучив одну, вы изучите и другую. Выбор определяется формой задачи: значение проходит через выражение, последовательность действий через оператор.
Часто задаваемые вопросы
Что такое switch-выражение в C#?
Switch-выражение, добавленное в C# 8, вычисляется в значение: string text = code switch { 200 => "OK", 404 => "Not Found", _ => "Unknown" };. Входное значение стоит перед ключевым словом switch, каждая ветвь имеет вид pattern => result, ветви разделяются запятыми, а ключевых слов case, break и default нет.
Что служит default в switch-выражении C#?
Шаблон отбрасывания _ совпадает с чем угодно, поэтому _ => "Unknown" в последней ветви играет роль default. Без ветви для всех остальных значений компилятор выдаёт предупреждение (CS8509), если какой-то вход не покрыт, а несовпавшее значение во время выполнения выбрасывает SwitchExpressionException.
Как сопоставить несколько значений в одной ветви switch-выражения?
Используйте шаблон or из C# 9: "sat" or "sun" => "weekend". Для диапазонов сочетайте реляционные шаблоны через and: >= 13 and <= 19 => "teen". В C# 8 значения перечисляют отдельными ветвями с одинаковым результатом.
Можно ли использовать when в switch-выражении C#?
Да. Ветвь может добавить условие после шаблона: Order o when o.Total > 100 => 0m. Ветвь совпадает, только если совпал шаблон и условие when истинно; иначе вычисление продолжается со следующей ветви.
Что использовать, switch-выражение или оператор switch?
Используйте выражение, когда каждая ветка только выдаёт значение, например сопоставляет статус с подписью или вычисляет цену. Используйте оператор, когда ветки выполняют несколько операторов, имеют побочные эффекты или должны выйти из цикла. Выражение также даёт предупреждения о полноте, которых нет у оператора.