1. Введение
Вы уже умеете писать такой код:
switch (color)
{
case "red":
Console.WriteLine("Стоп!");
break;
case "yellow":
Console.WriteLine("Ждите...");
break;
case "green":
Console.WriteLine("Можно идти!");
break;
default:
Console.WriteLine("А это что за цвет?");
break;
}
Но что если мы хотим присвоить переменной разное значение в зависимости от входного параметра, например, вернуть строку, основанную на числе из функции? До появления switch-выражений приходилось городить вот такие конструкции:
string meaning;
switch (number)
{
case 42:
meaning = "Ответ на главный вопрос жизни, вселенной и вообще всего";
break;
case 7:
meaning = "Счастливое число";
break;
default:
meaning = "Обычное число";
break;
}
Как-то не слишком элегантно для языка XXI века, правда? Хочется сразу вернуть значение, как это обычно бывает в функциональных языках.
Вот тут и появляются switch-выражения — одна из самых красивых (и свежих) возможностей C#, начиная с версии 8.0.
2. switch-выражения: синтаксис, который хочется обнять
Сравним: оператор и выражение
Классический switch — это statement ("оператор"), а switch-выражение — это expression, то есть оно вычисляет значение и его можно сразу присвоить переменной или вернуть из метода.
Структура switch-выражения:
var result = value switch
{
pattern1 => expression1,
pattern2 => expression2,
_ => defaultExpression // "подчёркивание" - это "иначе"
};
Например:
int number = 42;
string meaning = number switch
{
42 => "Ответ на главный вопрос жизни, вселенной и вообще всего",
7 => "Счастливое число",
_ => "Обычное число"
};
Console.WriteLine(meaning); // => Ответ на главный вопрос жизни, вселенной и вообще всего
Всё лаконично, читаемо, никакой портянки кода, ни одного break;.
Обратите внимание: подчёркивание _ — это "поймать все остальные случаи", аналог default.
3. switch-выражения и паттерны: как они сочетаются
Вот где начинается настоящее веселье. В switch-выражениях можно использовать не только обычные значения, но и паттерны: проверки по диапазону, типу, свойствам объектов, деконструкции (разбиении объекта на его части), и даже combinatorial patterns (and, or, not).
Примеры паттернов в switch-выражении
Константные паттерны
Это то, что мы выше уже делали: сравнение с 42, 7 и так далее.
Диапазоны и сравнения
int age = 17;
string category = age switch
{
< 18 => "Несовершеннолетний",
>= 18 and <= 65 => "Взрослый",
> 65 => "Пожилой человек",
_ => "Неизвестная возрастная категория"
};
Console.WriteLine(category);
Кстати, обратите внимание — можно использовать несколько условий вместе! C# становится всё выразительнее.
Типовые паттерны (Type Patterns)
Допустим, у нас есть объект, который может быть разного типа (например, object value;). Мы можем обработать разные типы вот так:
object value = 3.14;
string description = value switch
{
int i => $"Целое число: {i}",
double d => $"Дробное число: {d:F2}",
string s => $"Это строка: '{s}'",
null => "null!",
_ => "Что-то другое"
};
Console.WriteLine(description); // => Дробное число: 3.14
Связанные паттерны (Property Patterns и Positional Patterns)
С C# 8 и далее мы можем проверять значения свойств прямо в switch-выражении.
Пример с объектом
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
User user = new User { Name = "Анна", Age = 17 };
string welcome = user switch
{
{ Age: < 18 } => $"Здравствуй, {user.Name}! Ты подросток.",
{ Age: >= 18 and <= 65 } => $"Привет, {user.Name}! Взрослая жизнь прекрасна.",
{ Age: > 65 } => $"Добро пожаловать, {user.Name}! Вам всегда рады.",
_ => "Неизвестный возраст"
};
Console.WriteLine(welcome);
Здесь в фигурных скобках { Age: < 18 } — это property pattern, то есть мы проверяем не весь объект, а только его свойство.
4. Новые паттерны C# 11-14
C# стремится к лаконичности и гибкости. Недавно в нем появились combinatorial patterns: and, or, not, а также улучшились positional patterns. Давайте разберём их.
Логические паттерны: and, or, not
Вы можете составлять сложные условия, как в матлогике!
int temperature = 25;
string result = temperature switch
{
< 0 => "Мороз!",
>= 0 and < 20 => "Прохладно",
>= 20 and < 30 => "Комфортно",
>= 30 or <= -30 => "Экстрим!",
_ => "Странная температура"
};
Если хочется исключить случаи, пожалуйста:
int score = 80;
string grade = score switch
{
>= 90 => "Отлично",
>= 60 and not >= 90 => "Хорошо",
_ => "Плохо"
};
Деконструкция (Positional Patterns)
Если у вас структура или record, который поддерживает деконструкцию, C# позволяет разбивать объект прямо в switch-выражении:
public record Point(int X, int Y);
Point p = new Point(10, 20);
string desc = p switch
{
(0, 0) => "Начало координат",
(var x, 0) => $"На оси X, X={x}",
(0, var y) => $"На оси Y, Y={y}",
(var x, var y) when x == y => $"На диагонали: {x}",
_ => $"В точке ({p.X}, {p.Y})"
};
Console.WriteLine(desc); // => В точке (10, 20)
5. Когда и зачем использовать switch-выражения
В современном C# switch-выражения — это не просто модная "фича", а реальный способ избежать бумагомарания, сделать код компактнее, легче тестировать и поддерживать. Это особенно критично при написании парсеров, обработчиков команд, бизнес-логики, конфигурирования правил — везде, где нужно разветвлять исполнение по сложным критериям, в том числе не только по значениям, но и по типам, диапазонам, свойствам, комбинациям.
На собеседованиях
- Как бы вы обработали разные варианты ответа от пользователя?
- Как сделать так, чтобы вернуть разное значение функции, зависящее и от типа, и от значения?
- Как вы будете легко добавлять новые правила без кучи if-else?
Покажите switch-выражение с паттернами! Это сразу продемонстрирует ваше знание новых возможностей языка и хороший стиль кодинга.
Таблица: сравнение switch-оператора и switch-выражения
| Характеристика | switch statement | switch expression |
|---|---|---|
| Тип | оператор (statement) | выражение (expression) |
| Использование break | обязательно | не нужен |
| Может возвращать значение | только через переменную | да, прямо возвращает значение |
| Может использовать паттерны | частично | полностью (type, property, и др.) |
| Ловушка | можно забыть break | все пути должны вернуть значение |
6. Типичные ошибки и мелкие пакости
- Начинающие разработчики часто путаются в разнице между switch-оператором ("statement") и switch-выражением ("expression"). Например, забывают, что в switch-выражении не нужны break;, а все ветви обязательно должны возвращать значение.
- Если забыть ветку _, компилятор подскажет: "а если ничего не подошло?".
- В switch-выражении нельзя использовать goto (и слава богу!).
- Если дублируется условие (например, два одинаковых паттерна), компилятор это тоже не пропустит.
- Внимательно относитесь к типу значения, возвращаемого из switch-выражения: он должен быть совместим с типом переменной, куда вы это всё присваиваете.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ