
За годы своего развития C# существенно эволюционировал; одна из самых мощных фич языка — это сопоставление с образцом (pattern matching).
Работая недавно над небольшим хобби-проектом, я наткнулся на такую прекрасную строку кода C#.
if (person is not null and { Age: > 18 })
{}
Выглядит изящно. Откровенно говоря, она заставила меня призадуматься.
Годами я писал проверки на null
и свойства-аксессоры классическим образом:
if (person != null && person.Age > 18)
{}
Функционально? Да. Удобочитаемо? Не особо. Безопасно? Спорно, особенно когда код становится сложнее.
Я решил создать шорт YouTube об этом современном синтаксисе. Это небольшое забавное напоминание о том, что C# позволяет при помощи сопоставления с образцом комбинировать проверки на null
и обращение к свойству в одно условие.
Я понятия не имел, что это короткое видео приведёт к гораздо более глубокому исследованию, и покажет мне, насколько полезно и универсально сопоставление с образцом в современном C#.
Эта фича повышает читаемость, уменьшает объём бойлерплейта и обеспечивает более выразительную обработку логики.
В этой статье мы изучим различные типы образцов, поддерживаемых в C#, их работу и поймём, когда их использовать, на понятных примерах из реального мира.
Всё началось с is…
Меня одолевало любопытство.
Я уже знал о базовом ключевом слове is
в C#. Вероятно, вы видели его тысячу раз:
if (obj is string s)
{
Console.WriteLine($"It's a string: {s}");
}
Чего я не осознавал полностью, так это того, насколько ключевое слово is
эволюционировало, позволив обрабатывать типы, условия, свойства и даже сложные объекты; и всё это в одной строке.
Что такое сопоставление с образцом?
Сопоставление с образцом — это механизм, позволяющий сравнивать значение на входе с образцом и предпринимать действия, если оно ему соответствует. В C# сопоставление с образцом поддерживается в следующих конструкциях:
выражения
is
операторы
switch
выражения
switch
C# поддерживает широкий спектр образцов, каждый из которых мы рассмотрим один за другим.
1. Образцы объявления и типов
Эти образцы проверяют тип объекта в среде выполнения и опционально присваивают его новой переменной.
Пример:
object item = "Welcome!";
if (item is string text)
{
Console.WriteLine(text.ToUpper()); // Вывод: WELCOME!
}
Аналогия из реального мира: представьте, что покупатель — это объект. Если это PremiumCustomer
, то мы можем привести тип и применить особые правила.
2. Образцы констант
Их можно использовать для сравнения значения непосредственно с константой. Это более краткая альтернатива нескольким операторам if
.
Пример:
int guests = 2;
string table = guests switch
{
1 => "Single Table",
2 => "Couple Booth",
4 => "Family Table",
_ => "Group Table"
};
Например, ресторан может бронировать столы в зависимости от количества людей.
3. Образцы отношений
Эти образцы позволяют сравнивать значение и константу (меньше, больше или равно).
Пример:
double temperature = 35.2;
string category = temperature switch
{
< 0 => "Freezing",
>= 0 and < 20 => "Cold",
>= 20 and <= 30 => "Warm",
> 30 => "Hot",
_ => "Unknown"
};
Отлично подходит для таких сценариев, как категоризация погоды или систем оценок.
4. Логические образцы
Комбинирование образцов при помощи and
, or
и not
.
Пример:
int age = 25;
bool isYoungAdult = age is >= 18 and <= 30;
Для исключения можно использовать not
:
if (user is not null)
{
// user валиден
}
Логические образцы позволяют создавать более сложные деревья решений с меньшим количеством шума.
5. Образцы свойств
Сопоставление на основании значений свойств внутри объекта.
Пример:
var booking = new { RoomType = "Suite", Guests = 2 };
if (booking is { RoomType: "Suite", Guests: > 1 })
{
Console.WriteLine("Apply luxury tax.");
}
Это похоже на изучение полей записи для принятия решений о действиях.
6. Позиционные образцы
Деконструирование объектов или кортежей для сопоставления отдельных компонентов.
Пример:
(Point x, Point y) = (new(0, 0), new(1, 1));
string status = (x, y) switch
{
(0, 0) => "At origin",
(_, _) => "Somewhere else"
};
Отлично работает при моделировании координат, интервалов или систем сеток.
7. Образец var
Используйте его, когда вам не важен тип и вы просто хотите извлечь значение.
Пример:
if (GetData() is var data && data.Length > 0)
{
Console.WriteLine("Data retrieved!");
}
Это удобно при выполнении проверок или преобразований на лету.
8. Образец сброса
Используйте _
для сопоставления с чем угодно, если конкретное значение не важно.
Пример:
string GetDayStatus(DayOfWeek? day) => day switch
{
DayOfWeek.Monday => "Start of week",
DayOfWeek.Friday => "Almost weekend",
_ => "Another day"
};
Полезно для охватывающих всё fallback.
9. Образцы списков (C# 11+)
Позволяют проверять последовательности (например, массивы/списки) со вложенными образцами.
Примеры:
int[] ratings = { 5, 4, 3 };
if (ratings is [5, .., >= 3])
{
Console.WriteLine("Great feedback!");
}
Также можно извлекать срезы с помощью ..
.
if (ratings is [_, .. var middle, _])
{
Console.WriteLine($"Middle scores: {string.Join(", ", middle)}");
}
Полезно при валидации структурированных данных, например оценок за экзамены, массивов конфигураций и так далее.
Подведём итог
Тип образца | Сценарий использования |
---|---|
Объявление/тип | Проверка и приведение типов |
Константа | Сопоставление с точным значением |
Относительный | Сравнение с интервалом |
Логический | Сложные комбинации |
Свойство | Сопоставление значений свойств объекта |
Позиционный | Сопоставление на основании деконструкции |
Var | Извлечение значения |
Сброс | Сопоставление с чем угодно без учёта значения |
Список | Сопоставление массивов/списков и подпоследовательностей |
Когда следует использовать сопоставление с образцом?
Судя по моему опыту, преимущества сопоставления с образцом проявляются, когда:
Вы работаете с разнородными типами (например, с параметрами
object
).Необходимо избежать длинных проверок на null.
Нужно повысить читаемость условных операторов.
Выполняется обработка сложной логики ветвления (например, преобразования ответов API).
В заключение
Чем больше я практиковался, тем сильнее осознавал, что сопоставление с образцом в C# — это не просто синтаксический трюк, а смена образа мышления.
Вместо того чтобы писать логику в виде процедурных шагов, вы описываете её формы и правила. Вы говорите C#, чего ожидаете, и оставляете всю работу компилятору.
Сопоставление с образцом позволяет писать более чистый и удобный в поддержке код при создании парсеров, логики валидации или при упрощении условных конструкций.
Готовы двигаться дальше? Попробуйте отрефакторить один из своих недавних блоков кода, переполненных switch
или if
, превратив их в изящную логику на основе образцов.