Pattern matching и switch expressions
↑ 1.3 C♯: основы языка · ← Предыдущая · → Следующая
Зачем это на собесе
pattern matching делает код компактным и безопасным: маппинг статусов, обработка разных типов ресурсов, валидация. Современный C#-код ждут на собесе, а не цепочки if/else с приведениями.
Объяснение
Виды паттернов
object value = GetValue();
// тип + переменная
if (value is Patient p) Console.WriteLine(p.LastName);
// константа и null
if (value is null) return;
if (status is AppointmentStatus.Cancelled) { ... }
// отрицание, и/или
if (value is not string) { ... }
if (age is >= 18 and < 65) { ... } // relational + логический
if (code is "A" or "B" or "C") { ... }
// property pattern — проверка свойств (в т. ч. вложенных)
if (patient is { Address.City: "Минск", BirthDate.Year: < 2000 }) { ... }
// positional (для типов с Deconstruct, records)
if (point is (0, 0)) Console.WriteLine("origin");
// list patterns (C# 11)
if (args is [var first, .., var last]) { ... }
if (codes is ["ICD-10", _, ..]) { ... }switch expression
string ToRu(AppointmentStatus s) => s switch
{
AppointmentStatus.Booked => "Записан",
AppointmentStatus.Arrived => "Пришёл",
AppointmentStatus.Cancelled => "Отменён",
_ => throw new ArgumentOutOfRangeException(nameof(s), s, null),
};
decimal Discount(Patient p) => p switch
{
{ Age: >= 65 } => 0.2m,
{ IsVeteran: true } => 0.15m,
{ Age: < 18, HasInsurance: true } => 0.1m,
null => throw new ArgumentNullException(nameof(p)),
_ => 0m,
};
// обработка разных типов ресурсов (как в фабриках, мапперах)
string Describe(IResource r) => r switch
{
Patient p => $"Пациент {p.LastName}",
Encounter { Status: "finished" } e => $"Визит {e.Id} завершён",
Encounter e => $"Визит {e.Id}",
Observation o when o.Value > o.Threshold => "Отклонение от нормы",
_ => r.GetType().Name,
};switch statement с паттернами
switch (ex)
{
case PatientNotFoundException nf: return Results.NotFound(nf.Message);
case ValidationException { Errors.Count: > 0 } ve: return Results.ValidationProblem(ve.ToDictionary());
case OperationCanceledException: return Results.StatusCode(499);
default: throw ex;
}Компилятор проверяет полноту switch expression по enum и типам и предупреждает о недостижимых ветках (порядок важен: частное выше общего).
Нюансы и подводные камни
- Switch expression без
_бросаетSwitchExpressionExceptionна непредусмотренном значении. Лучше явно_ => throw ...с понятным сообщением. - Большие switch по типам — возможный сигнал, что нужен полиморфизм (метод в каждом типе) или паттерн Strategy.
isс property pattern безопасен к null:x is { Name: "A" }вернёт false для null.- Паттерны не работают с произвольными выражениями справа: только константы, типы, relational-операторы.
Тестирование
[Theory]
[InlineData(70, false, 0.2)]
[InlineData(30, true, 0.15)]
[InlineData(30, false, 0)]
public void Discount_rules(int age, bool veteran, decimal expected) =>
Discount(new Patient { Age = age, IsVeteran = veteran }).Should().Be(expected);Вопросы с ответами
Какие виды паттернов есть в C#?
Type, constant (включая null), relational (<, >=), логические (and, or, not), property, positional (через Deconstruct), list patterns, var и discard (_).
Чем switch expression лучше switch statement?
Это выражение: возвращает значение, компактнее, нет break, компилятор проверяет полноту и недостижимые ветки. Хорошо сочетается с паттернами.
Как безопасно проверить вложенное свойство на значение, если объект может быть null?
Property pattern:
x is { Address.City: "Минск" }— вернёт false, если x или Address равны null.
Когда pattern matching по типам — плохой дизайн?
Когда большой switch по типам нужно дополнять при каждом новом типе и он дублируется в разных местах. Лучше полиморфизм или Strategy, где поведение лежит в самих типах.
Связанные темы
- Предыдущая: Enum, record, tuple, деконструкция · Следующая: Equals, GetHashCode, сравнение объектов
- Enum и record: Enum, record, tuple, деконструкция
- Полиморфизм: Полиморфизм и инкапсуляция на практике
- Поведенческие паттерны (Strategy): Поведенческие паттерны: Strategy, Chain of Responsibility, Command, Observer, Template Method, Mediator
- Narrowing в TypeScript (аналог): Narrowing и type guards