Оператор switch сравнивает одно значение со списком константных меток case и выполняет совпавший раздел. В C# каждый раздел должен заканчиваться break (или другим оператором, который из него выходит), поэтому один case никогда не может молча перейти в следующий.
Вывод:
200: OK
302: redirect
404: not found
500: unhandled status
Значение в скобках вычисляется один раз и сравнивается с каждой меткой. default выполняется, когда ничего не совпало; он необязателен и может стоять в любом месте списка, хотя обычно идёт последним. Если совпадения нет и default отсутствует, весь switch ничего не делает.
Каждому разделу нужен break
В C, C++, Java и JavaScript забытый break заставляет выполнение соскользнуть в следующий case. C# превращает эту ошибку в ошибку компиляции:
switch (level)
{
case 1:
Console.WriteLine("read");
// error CS0163: Control cannot fall through from one case label ('case 1:') to another
case 2:
Console.WriteLine("write");
break;
}
Правило такое: конец списка операторов раздела не должен быть достижим. Обычно это правило выполняют через break, но подходит всё, что выходит из раздела:
returnвыходит из всего метода, и внутри небольшого вспомогательного метода это часто самая аккуратная форма.throwвыбрасывает исключение для значения, которое никогда не должно встретиться.continueпереходит к следующей итерации, когдаswitchнаходится внутри цикла.goto caseиgoto defaultнамеренно переходят в другой раздел.
Последний раздел не исключение. default: в конце без break завершается ошибкой CS8070, Control cannot fall out of switch from final case label ('default:').
Несколько значений в одном case
Метки без операторов между ними разделяют раздел, стоящий под ними. Это единственный вид проваливания, который разрешает C#, потому что в пустой метке не может скрываться код:
Вывод:
'O' is a vowel
'k' is a consonant or symbol
' ' is a space
'7' is a digit
Каждый раздел здесь заканчивается return, поэтому break не нужен (break после return был бы недостижимым кодом). Две метки с одинаковым значением это ошибка CS0152 The switch statement contains multiple cases with the label value '1', а каждая метка должна быть константой времени компиляции: переменная в case вызывает ошибку «a constant value is expected».
switch по строке
После целых чисел строки это самое частое значение в switch. Сравнение порядковое и с учётом регистра, поэтому нормализуйте входные данные перед switch:
Вывод:
Starting the server
Stopping the server
Server is running
Unknown command: restart
Unknown command: (none)
Без ToLowerInvariant значение "STOP" попало бы в default. Значение null в switch допустимо: оно не совпадает ни с одной строковой константой и уходит в default, поэтому сам switch никогда не выбрасывает исключение.
switch по перечислению
Перечисления это то, для чего switch и создавался: закрытый набор именованных значений.
Вывод:
Pending: wait for payment
Paid: pack and ship
Shipped: track the parcel
Delivered: nothing, order is closed
Cancelled: nothing, order is closed
ArgumentOutOfRangeException for value 42
Компилятор не проверяет, что оператор switch охватывает все члены перечисления, а переменная перечисления может хранить любое целое число базового типа ((OrderStatus)42 допустимо). default, который выбрасывает исключение, превращает забытый case в немедленную и очевидную ошибку вместо молчаливого бездействия.
goto case и goto default
Когда один case действительно должен продолжиться в другом, C# заставляет написать переход:
Вывод:
admin:
manage users
edit posts
read posts
editor:
edit posts
read posts
guest:
read posts
goto case принимает константу, которая должна совпадать с одной из меток того же switch. В реальном коде он редок: общее поведение обычно понятнее вынести в метод, который вызывают несколько case. Тем не менее это ответ на вопрос «как провалиться в следующий case в C#».
Переменные внутри case
Весь блок switch это одна область видимости, поэтому два case не могут объявить переменную с одинаковым именем:
switch (shape)
{
case "square":
int area = side * side;
break;
case "rectangle":
int area = width * height; // error CS0128: A local variable or function named 'area' is already defined in this scope
break;
}
Заключите тело каждого раздела в фигурные скобки, чтобы дать ему собственную область видимости: case "square": { int area = side * side; ... break; }.
break внутри цикла
break в case выходит из switch, а не из окружающего цикла. continue делает обратное: switch не является циклом, поэтому continue переходит к следующей итерации цикла.
Вывод:
end marker found
Total: 134
Цикл продолжился после "end" и добавил 99. Чтобы остановить цикл изнутри switch, установите флаг и проверьте его после switch или вынесите цикл в метод и используйте return. Варианты сравниваются на странице о break и continue.
Метки case с шаблонами и when
Начиная с C# 7.0 метка case может быть шаблоном, а не константой: она может проверять тип значения и добавлять условие when. Эта форма распространена в современном коде на C#:
static string Describe(object value)
{
switch (value)
{
case null:
return "nothing";
case int n when n < 0:
return $"negative int {n}";
case int n:
return $"int {n}";
case string s when s.Length == 0:
return "empty string";
case string s:
return $"string \"{s}\"";
default:
return $"other: {value.GetType().Name}";
}
}
С шаблонами порядок важен: побеждает первый совпавший case, а компилятор сообщает об ошибке, если case никогда не может быть достигнут, потому что предыдущий уже его покрывает (размещение case int n: выше case int n when n < 0: не компилируется). Условие when работает и с константными case: case 0 when isAdmin:. C# 9 добавил реляционные и логические шаблоны, поэтому case может выглядеть как case >= 90: или case 'a' or 'e' or 'i':.
В C# 7 без шаблонов та же диспетчеризация по типу записывается цепочкой if с шаблоном типа is:
Вывод:
negative int -3
int 12
empty string
string "hi"
other: Decimal
nothing
Когда каждая ветка switch только вычисляет значение, switch-выражение ещё короче: var grade = score switch { >= 90 => "A", >= 80 => "B", _ => "C" };. Его синтаксис, шаблоны и правила полноты описаны на странице о switch-выражении.
По чему можно делать switch
| Тип значения | Константные case | Примечания |
|---|---|---|
int, long, byte и другие целые | да | классический случай |
char | да | case 'a': |
string | да | порядковое сравнение с учётом регистра, null уходит в default |
bool | да | допустимо, но if понятнее |
любой enum | да | нет проверки полноты |
double, decimal | начиная с C# 7 | только константы; для диапазонов нужны реляционные шаблоны C# 9 |
| любой другой тип | начиная с C# 7 | через шаблоны типов, case Circle c: |
Часто задаваемые вопросы
Обязателен ли break в switch в C#?
Каждый раздел case должен заканчиваться оператором, который из него выходит: обычно break, но подходят и return, throw, continue (внутри цикла) или goto case. Переход с конца одного раздела в следующий это ошибка компиляции CS0163 Control cannot fall through from one case label ('case 1:') to another, и это касается и последнего раздела.
Как обработать несколько значений в одном case в C#?
Поставьте метки подряд без ничего между ними: case "sat": case "sun": Console.WriteLine("weekend"); break;. Пустые метки case могут переходить к следующей метке; заканчиваться break обязан только раздел, в котором есть операторы. В C# 9 и новее case с шаблоном может также выглядеть как case 1 or 2 or 3:.
Учитывает ли switch по строке регистр в C#?
Да. Строковые case сравниваются порядково с учётом регистра, поэтому "Yes" не совпадёт с case "yes":. Сначала нормализуйте значение: switch (input.Trim().ToLowerInvariant()). Строка null не совпадает ни с одной константой и попадает в default.
Может ли switch в C# проваливаться в следующий case, как в C или Java?
Неявно нет. Если вы хотите, чтобы один case продолжился в другом, скажите это явно через goto case <value>; или goto default;. Так каждый переход виден в коде, и именно поэтому C# запрещает молчаливый вариант.
Чем оператор switch отличается от switch-выражения в C#?
Оператор switch выполняет операторы и требует case, break и default. Switch-выражение (C# 8 и новее) выдаёт значение: var label = n switch { 0 => "zero", > 0 => "positive", _ => "negative" };. Используйте выражение, когда каждая ветка только вычисляет результат.