Pattern matching и switch в C# с примерами
Сопоставление с образцом (pattern matching) позволяет проверить форму значения и сразу достать из него нужные данные: тип, свойства, диапазон, элементы списка. Главные инструменты - оператор is, инструкция switch и выражение switch. Ниже разобраны все виды шаблонов, от типов и свойств до логических комбинаций and, or, not и шаблонов списков.

Содержание
Что даёт сопоставление с образцом
Раньше проверка типа и приведение требовали нескольких строк: сначала is, затем явное приведение, потом проверка полей. Шаблоны объединяют это в одно выражение. Компилятор проверяет форму значения, а при совпадении создаёт переменную нужного типа, готовую к использованию.
Шаблоны работают в трёх местах: в операторе is, в инструкции switch и в выражении switch. Набор шаблонов везде одинаков, поэтому достаточно выучить его один раз.
Оператор is: проверка типа и значения
Самый простой шаблон - тип. Запись obj is string s проверяет тип и при успехе кладёт значение в переменную s. Переменная доступна только там, где компилятор уверен, что шаблон совпал.
object value = "привет";
if (value is string text && text.Length > 3)
Console.WriteLine($"Строка длиной {text.Length}");
if (value is not null)
Console.WriteLine("значение задано");
if (value is not int)
Console.WriteLine("это не число");
Проверка на null через is null надёжнее, чем == null, потому что перегруженный оператор равенства её не обманет. Про типичные причины исключений при пустых ссылках есть отдельная статья о NullReferenceException.
Выражение switch
Выражение switch возвращает значение, поэтому его можно присвоить переменной или вернуть из метода. Каждая ветка записывается как образец => результат, ветки разделяются запятыми, а _ означает "любое остальное значение". Если ни одна ветка не подошла, выбрасывается SwitchExpressionException, поэтому ветку по умолчанию обычно добавляют.
static string DayType(DayOfWeek day) => day switch
{
DayOfWeek.Saturday or DayOfWeek.Sunday => "выходной",
_ => "рабочий день"
};
static int Score(string grade) => grade switch
{
"A" => 5,
"B" => 4,
"C" => 3,
_ => 0
};
Console.WriteLine(DayType(DayOfWeek.Sunday)); // выходной
Console.WriteLine(Score("B")); // 4
Отличия от классической инструкции switch: нет слов case и break, ветка всегда возвращает значение, а компилятор предупреждает, если не все варианты перечисления покрыты.
Шаблон типа
Шаблон типа сопоставляет значение с классом или интерфейсом. Это удобная замена цепочке if ... is ... else if. Порядок веток важен: сначала пишут более частные типы, затем общие, иначе общая ветка перекроет частную, и компилятор сообщит об ошибке.
abstract record Shape;
record Circle(double Radius) : Shape;
record Rectangle(double Width, double Height) : Shape;
record Triangle(double Base, double Height) : Shape;
static double Area(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
Triangle t => 0.5 * t.Base * t.Height,
null => throw new ArgumentNullException(nameof(shape)),
_ => throw new NotSupportedException()
};
Console.WriteLine(Area(new Rectangle(3, 4))); // 12
Если тип в ветке не нужен, переменную можно опустить: Circle => "круг". Это называется шаблоном типа без объявления, а ещё его можно записать как Circle _.
Шаблон свойств
Шаблон свойств проверяет значения свойств объекта в фигурных скобках. Проверять можно вложенные свойства через точку, что делает запись компактной.
record Address(string City, string Country);
record Customer(string Name, int Age, Address Address, bool IsVip);
static decimal DeliveryPrice(Customer c) => c switch
{
{ IsVip: true } => 0m,
{ Address.Country: "Россия", Address.City: "Москва" } => 200m,
{ Address.Country: "Россия" } => 450m,
{ Address: null } => 1000m,
_ => 1500m
};
var customer = new Customer("Ирина", 29, new Address("Москва", "Россия"), false);
Console.WriteLine(DeliveryPrice(customer)); // 200
Если свойств много, можно сочетать проверку типа и свойств в одной ветке: Circle { Radius: 0 } => "вырожденный круг". Для записей и кортежей доступен позиционный шаблон, где значения сопоставляются по порядку компонентов.
static string Describe(Shape s) => s switch
{
Circle { Radius: 0 } => "вырожденный круг",
Rectangle(var w, var h) when w == h => "квадрат",
Rectangle(var w, var h) => $"прямоугольник {w} на {h}",
_ => "другая фигура"
};
static string Quadrant(int x, int y) => (x, y) switch
{
(0, 0) => "начало координат",
(> 0, > 0) => "первая четверть",
(< 0, > 0) => "вторая четверть",
(< 0, < 0) => "третья четверть",
(> 0, < 0) => "четвёртая четверть",
_ => "на оси"
};
Реляционные шаблоны
Реляционные шаблоны сравнивают значение через <, >, <= и >= с константой. Они заменяют длинные цепочки условий для диапазонов и работают с числами, символами и перечислениями.
static string Temperature(int degrees) => degrees switch
{
< -20 => "лютый мороз",
< 0 => "мороз",
< 15 => "прохладно",
< 25 => "тепло",
_ => "жарко"
};
Console.WriteLine(Temperature(-5)); // мороз
Console.WriteLine(Temperature(30)); // жарко
Ветки проверяются сверху вниз, поэтому условие "меньше нуля" автоматически означает "не меньше -20". Это удобно, но требует внимания к порядку.
Логические шаблоны and, or, not
Логические комбинаторы объединяют шаблоны. and требует совпадения обоих, or - любого из двух, not инвертирует. Скобки группируют части. Обратите внимание: это ключевые слова, а не операторы && и ||, и применяются они к шаблонам, а не к булевым выражениям.
static bool IsLetterOrDigit(char c) =>
c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or (>= '0' and <= '9');
static string AgeGroup(int age) => age switch
{
< 0 or > 130 => "некорректный возраст",
< 14 => "ребёнок",
>= 14 and < 18 => "подросток",
>= 18 and < 65 => "взрослый",
_ => "пожилой"
};
object o = 5;
if (o is not null and int)
Console.WriteLine("число");
string? name = null;
if (name is not (null or ""))
Console.WriteLine(name);
Шаблоны списков
Начиная с C# 11 массивы и списки можно сопоставлять по структуре. Квадратные скобки описывают элементы по порядку, _ означает любой элемент, а шаблон среза .. заменяет любое количество элементов, в том числе ноль. Чтобы получить срез в переменную, пишут .. var rest.
static string Check(int[] numbers) => numbers switch
{
[] => "пустой массив",
[var single] => $"один элемент: {single}",
[1, 2, 3] => "ровно 1, 2, 3",
[var first, .., var last] when first == last => "первый равен последнему",
[0, ..] => "начинается с нуля",
[.., > 100] => "заканчивается числом больше 100",
[_, _, ..] => "два элемента или больше"
};
Console.WriteLine(Check(new[] { 7 })); // один элемент: 7
Console.WriteLine(Check(new[] { 4, 5, 4 })); // первый равен последнему
static string Command(string[] args) => args switch
{
["start", var name] => $"запуск {name}",
["stop", ..] => "остановка",
[] => "нет команд",
_ => "неизвестная команда"
};
Шаблоны списков работают с любым типом, у которого есть Length или Count и индексатор. Для коллекций с поиском по ключу существуют другие структуры, например словарь Dictionary.
Условие when и классический switch
Ветку можно дополнить условием when, если нужно проверить то, что нельзя выразить шаблоном. Классическая инструкция switch получила те же шаблоны, и в ней тоже можно писать case int n when n > 0:. Для простого выбора действия инструкция по-прежнему подходит, а вот когда нужно вычислить значение, выражение switch короче.
static void Handle(object message)
{
switch (message)
{
case int n when n > 100:
Console.WriteLine("большое число");
break;
case int n:
Console.WriteLine($"число {n}");
break;
case string { Length: 0 }:
Console.WriteLine("пустая строка");
break;
case string s:
Console.WriteLine($"строка {s}");
break;
case null:
Console.WriteLine("null");
break;
default:
Console.WriteLine("что-то иное");
break;
}
}
Если в проекте есть иерархия классов, удобно комбинировать шаблоны с наследованием, как в статье про работу с классами.
Типичные ошибки
- Неверный порядок веток: общий шаблон стоит выше частного, и компилятор сообщает, что ветка недостижима.
- Забытая ветка
_илиdefault. В выражении switch это приводит к исключению в рантайме для незапланированных значений. - Попытка использовать переменную из шаблона вне области, где шаблон гарантированно совпал.
- Путаница между
orв шаблоне и||в условии. Записьx is 1 || 2не скомпилируется, нужноx is 1 or 2. - Попытка сравнивать с неконстантой: в шаблоне можно использовать только константы, а не переменные. Для сравнения с переменной используйте
when. - Слишком сложные ветки с пятью условиями: код становится нечитаемым, лучше вынести часть логики в метод.
Частые вопросы
Чем switch expression отличается от обычного switch?
Выражение возвращает значение и записывается короче, без case и break. Инструкция выполняет блоки кода и удобнее, когда в ветках несколько действий.
Что означает символ подчёркивания в ветке?
Это шаблон-отброс. Он совпадает с любым значением и обычно стоит в последней ветке как вариант по умолчанию.
С какой версии C# доступны шаблоны?
Базовые шаблоны появились в C# 7, реляционные и логические - в C# 9, шаблоны списков - в C# 11. Для всех примеров достаточно .NET 8.
Можно ли использовать pattern matching со строками?
Да, строки сопоставляются как константы, а в шаблоне свойств можно проверять длину. Сопоставление с регулярным выражением шаблонами не поддерживается.
Влияет ли switch expression на производительность?
Компилятор оптимизирует ветки, для константных значений может строить таблицу переходов. Разница с цепочкой if обычно несущественна, выбирать стоит по читаемости.
Что будет, если ни одна ветка не подошла?
Выражение выбросит SwitchExpressionException. Компилятор заранее выдаёт предупреждение, если считает набор веток неполным.





