Menu

Оператор switch в C#: case, break, default и несколько значений в case

Как работает оператор switch в C#: switch по значениям int, string, char и enum, почему каждому case нужен break (ошибка CS0163), несколько меток case для нескольких значений, goto case, default, чувствительность к регистру строк и метки case с шаблонами и when.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Оператор 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" };. Используйте выражение, когда каждая ветка только вычисляет результат.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ