This lesson is not translated yet – the Russian original is below.
Когда веток много и все они сравнивают одну и ту же величину, цепочка else if расползается.
Для этого случая есть switch – и в современном C# у него два лица.
Классический switch
int day = 3;
switch (day)
{
case 1:
Console.WriteLine("Понедельник");
break;
case 2:
Console.WriteLine("Вторник");
break;
case 6:
case 7:
Console.WriteLine("Выходной");
break;
default:
Console.WriteLine("Обычный день");
break;
}
break завершает ветку. Две метки подряд (case 6: и case 7:) означают «или то, или другое» –
это единственный разрешённый вид объединения.
В C и Java, если забыть break, выполнение продолжится в следующей ветке – ошибка настолько частая, что у неё есть имя, «проваливание». В C# компилятор запрещает такую запись: ветка обязана завершиться явно. Забыть break здесь нельзя, программа не соберётся.
default срабатывает, когда не подошло ничего. Он не обязателен, но без него ситуация «не подошло
ни одно значение» проходит молча – а это редко то, чего вы хотели.
Выражение switch
Чаще switch нужен не чтобы что-то сделать, а чтобы выбрать значение. С C# 8 для этого есть
короткая форма:
string name = day switch
{
1 => "Понедельник",
2 => "Вторник",
6 or 7 => "Выходной",
_ => "Обычный день"
};
Отличий от классической формы четыре: имя переменной идёт перед словом switch, вместо
case и двоеточий – стрелки, ветки разделены запятыми, а default превратился в _
(«что угодно»).
Ветка _ обязательна, если компилятор не может доказать, что остальные покрывают все случаи. Логика простая: выражение обязано вернуть значение, а вернуть «ничего» оно не умеет.
Как это выглядит в других языках
Тот же выбор значения по номеру дня:
string name = day switch
{
1 => "Понедельник",
6 or 7 => "Выходной",
_ => "Обычный день"
};
match day:
case 1:
name = "Понедельник"
case 6 | 7:
name = "Выходной"
case _:
name = "Обычный день"
Совпадение неслучайно: обе конструкции выросли из сопоставления с образцом в функциональных языках, и даже подчёркивание для «чего угодно» пришло оттуда же. Разница в том, что в C# это выражение – оно возвращает значение, поэтому имя переменной пишется один раз, а не в каждой ветке.
Сопоставление с образцом
Всё интересное начинается, когда в case попадает не константа, а образец.
Диапазоны – с помощью when или сравнений прямо в ветке:
string grade = score switch
{
>= 90 => "отлично",
>= 75 => "хорошо",
>= 60 => "удовлетворительно",
_ => "неудовлетворительно"
};
Ветки проверяются сверху вниз, поэтому нижние границы указывать не нужно – до >= 75 дойдёт
только то, что меньше 90.
Проверка типа – если переменная объявлена как object, можно спросить, что в ней:
static string Describe(object value) => value switch
{
int n when n < 0 => $"отрицательное целое {n}",
int n => $"целое {n}",
string s => $"строка длиной {s.Length}",
null => "пусто",
_ => "что-то другое"
};
Здесь int n делает две вещи сразу: проверяет тип и объявляет переменную нужного типа. Приводить
руками не нужно – это уже сделано.
Слово when добавляет к образцу дополнительное условие. Порядок веток снова важен: int n when n < 0 обязана стоять выше, чем просто int n, иначе до неё не дойдёт очередь.
Тот же образец в обычном if
Проверка типа с объявлением переменной работает и вне switch:
if (value is int number)
{
Console.WriteLine(number * 2);
}
Оператор is проверяет тип и, если совпало, кладёт значение в новую переменную. Без него
пришлось бы писать проверку и приведение отдельно – две строки вместо одной и риск ошибиться при
приведении.
Отрицание записывается через is not:
if (value is not null)
{
Console.WriteLine(value);
}
is not null предпочтительнее, чем != null: для классов оператор != может быть переопределён автором класса и делать что угодно, а is переопределить нельзя – он всегда означает ровно то, что написано.
Когда switch, а когда if
switch уместен, когда все ветки задают вопрос об одной и той же величине. Если условия
разнородные – про возраст, про наличие паспорта и про день недели, – это работа для if.
Ещё один признак: если веток две, switch только удлиняет запись. Он начинает окупаться
примерно с трёх.