Сопоставление с образцом (pattern matching) проверяет значение на соответствие форме: «это Circle?», «это null?», «его Status равен "paid", а Total больше 100?». Когда проверка успешна, шаблон может ещё и извлечь части значения в новые переменные. Возможность начиналась скромно в C# 7 с оператора is и росла в каждой следующей версии, поэтому на этой странице указана версия каждой формы.
Шаблон типа с is (C# 7)
До C# 7 проверка типа и его использование занимали два шага: as плюс проверка на null или is плюс приведение. Шаблон типа делает и то и другое сразу и даёт вам типизированную переменную:
Вывод:
card ending 4242: 49.90
transfer from PT50...: 1200
no payment
other payment: 5
p is CardPayment card истинно, только когда p это CardPayment (или производный от него класс), отличный от null, и тогда card хранит его с правильным статическим типом. Значение null никогда не совпадает с шаблоном типа, поэтому платёж null проваливается до проверки на null.
Область видимости и определённое присваивание
Переменная шаблона существует в объемлющем блоке, но компилятор позволяет читать её только там, где совпадение заведомо успешно. Благодаря этому работает форма раннего выхода, когда вы выходите пораньше, а переменную используете потом:
Вывод:
19.99
42 is over 40
price must be text
boxed is int n && n > 40 показывает другую распространённую форму: переменная доступна справа от &&, потому что эта сторона выполняется, только если совпадение успешно. Справа от || она не присвоена, и компилятор об этом говорит (CS0165).
Шаблоны также распаковывают: boxed is int n успешно для упакованного int и даёт значение без приведения, которое могло бы выбросить исключение. Для типов значений, допускающих null, maybe is int n успешно ровно тогда, когда у maybe есть значение.
Шаблоны констант и null
Константа тоже является шаблоном. x is null используется чаще всего, а C# 7 также разрешает x is 0, status is "paid" или член перечисления:
if (customer is null) return; // null check that ignores any == overload
if (count is 0) Console.WriteLine("empty");
if (level is LogLevel.Error) Alert();
Многие команды предпочитают is null вместо == null, потому что его нельзя перенаправить перегрузкой оператора у типа. C# 9 добавляет отрицательную форму is not null.
Шаблоны в операторах switch (C# 7)
Те же шаблоны типа могут служить метками case, а условие when добавляет проверку. Case проверяются сверху вниз, поэтому упорядочивайте их от конкретных к общим:
static string Describe(object o)
{
switch (o)
{
case null: return "nothing";
case int n when n < 0: return "negative number";
case int n: return $"number {n}";
case string s when s.Length == 0: return "empty text";
case string s: return $"text of {s.Length} chars";
case IEnumerable<int> list: return "a sequence of numbers";
default: return o.GetType().Name;
}
}
Describe(-4); // negative number
Describe(""); // empty text
Describe(new List<int>()); // a sequence of numbers
Describe(2.5); // Double
Компилятор проверяет порядок: case, который никогда не может совпасть, потому что его покрывает предыдущий, это ошибка CS8120. default всегда проверяется последним, где бы он ни стоял. Та же логика, записанная цепочками if/is, как в примере с платежами выше, работает в любой версии C# начиная с 7.
Switch-выражения (C# 8)
Switch-выражение это компактная форма для «вычислить одно значение из нескольких случаев». Сначала значение, затем switch, затем ветви pattern => result через запятую, с _ для всех остальных:
public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;
static double Area(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle { Width: var w, Height: var h } => w * h,
_ => throw new ArgumentException("Unknown shape", nameof(shape)),
};
Area(new Rectangle(3, 4)); // 12
Если во время выполнения не совпала ни одна ветвь, выбрасывается SwitchExpressionException, а компилятор предупреждает (CS8509), когда видит, что какой-то вход не обработан. Синтаксис описан на отдельной странице о switch-выражении; остальная часть этой страницы о шаблонах, которые можно ставить в ветви.
Шаблоны свойств (C# 8, расширены в C# 10)
Шаблон свойства сопоставляет свойства объекта с вложенными шаблонами в фигурных скобках. Объект при этом должен быть не null, поэтому один { } означает «не null»:
public record Address(string City, string Country);
public record Order(decimal Total, string Status, Address ShipTo, int Items);
static string Shipping(Order order) => order switch
{
{ Status: "cancelled" } => "no shipment",
{ ShipTo.Country: "PT", Total: >= 50m } => "free, domestic", // C# 10 dotted form
{ ShipTo.Country: "PT" } => "4.90, domestic",
{ Total: > 200m } => "free, international",
_ => "12.00, international",
};
Shipping(new Order(60m, "paid", new Address("Porto", "PT"), 2)); // free, domestic
Shipping(new Order(260m, "paid", new Address("Lyon", "FR"), 2)); // free, international
В C# 8 и 9 обязательна вложенная форма: { ShipTo: { Country: "PT" } }. C# 10 разрешает сокращение через точку, использованное выше. Шаблоны свойств работают и с is, благодаря чему проверки нескольких условий читаются как описание: if (order is { Status: "paid", Items: > 0 }).
Реляционные и логические шаблоны (C# 9)
C# 9 добавил сравнения (<, <=, >, >=) и комбинаторы and, or и not, которые превращают диапазоны в шаблоны:
static string Grade(int score) => score switch
{
< 0 or > 100 => "invalid",
>= 90 => "A",
>= 75 and < 90 => "B",
>= 50 => "C",
_ => "F",
};
Grade(82); // B
Grade(101); // invalid
if (input is not null and not "") { ... }
if (c is >= 'a' and <= 'z' or >= 'A' and <= 'Z') { ... } // and binds tighter than or
Эквивалент на C# 7 это цепочка if/else if или switch с условиями when (case int s when s >= 90:).
Шаблоны кортежей и позиционные шаблоны (C# 8)
Switch сразу по нескольким значениям делается через switch по кортежу:
static string Quadrant(int x, int y) => (x, y) switch
{
(0, 0) => "origin",
(> 0, > 0) => "I",
(< 0, > 0) => "II",
(< 0, < 0) => "III",
(> 0, < 0) => "IV",
_ => "on an axis",
};
Quadrant(-2, 5); // II
Позиционный шаблон делает то же для любого типа с методом Deconstruct, включая записи: case Point(0, 0): или p is Rectangle(var w, var h) && w == h.
Шаблоны списков (C# 11)
Шаблоны списков сопоставляют массивы и списки по их элементам. .. совпадает с любым числом элементов («срез»), а в позиции каждого элемента может стоять любой другой шаблон:
static string Route(string[] args) => args switch
{
[] => "help",
["add", var item] => $"add {item}",
["remove", var item, ..] => $"remove {item}",
[var cmd, ..] => $"unknown command {cmd}",
};
Route(new[] { "add", "milk" }); // add milk
Route(new[] { "remove", "milk", "now" }); // remove milk
int[] nums = { 1, 2, 3, 4 };
if (nums is [1, .., var last]) Console.WriteLine(last); // 4
В какой версии что появилось
| Шаблон | Пример | Версия |
|---|---|---|
| Шаблон типа с переменной | o is Circle c | C# 7.0 |
| Шаблон константы | x is null, n is 0 | C# 7.0 |
Шаблон var | x is var v | C# 7.0 |
Шаблоны в метках case, условия when | case int n when n < 0: | C# 7.0 |
Switch-выражение, отбрасывание _ | x switch { ... } | C# 8 |
| Шаблон свойства | { Status: "paid" } | C# 8 |
| Шаблон кортежа | (x, y) switch { (0, 0) => ... } | C# 8 |
| Позиционный шаблон | Point(0, 0) | C# 8 |
| Реляционный шаблон | > 100, <= 0 | C# 9 |
| Логические шаблоны | not null, >= 1 and <= 5, 'a' or 'b' | C# 9 |
| Голый шаблон типа | case Circle:, Circle => ... | C# 9 |
| Расширенный шаблон свойства | { ShipTo.City: "Porto" } | C# 10 |
| Шаблон списка и срез | [first, .., last] | C# 11 |
Частые ошибки
- Порядок case от общих к конкретным.
case Payment p:передcase CardPayment c:делает второй недостижимым (CS8120). Сначала конкретные. - Ожидание, что шаблон типа совпадёт с null.
null is string sложно. Обрабатывайтеnullотдельным case или черезis null. - Чтение переменной шаблона там, где совпадение могло не произойти. После
a is int n || ...переменнаяnне присвоена (CS0165). - Длинные switch по типам вместо виртуальных методов. Если каждый новый подкласс требует правки одного и того же switch в пяти местах, поведению место в иерархии классов. Шаблоны хороши для данных извне (формы JSON, сообщения, кортежи входных данных) и для закрытых наборов типов, которые вы контролируете.
- Забытая ветвь для всех остальных в switch-выражении. Несовпавший вход выбрасывает исключение во время выполнения; читайте предупреждение CS8509.
Часто задаваемые вопросы
Что такое сопоставление с образцом в C#?
Сопоставление с образцом проверяет значение на соответствие форме и при совпадении может в том же шаге извлечь его части в переменные. if (shape is Circle c) проверяет тип и даёт вам типизированную c; order is { Status: "paid", Total: > 100 } проверяет свойства. Шаблоны используются в выражениях is, операторах switch и switch-выражениях.
Как использовать is с переменной в C#?
if (obj is Customer c) { ... } проверяет, что obj это Customer (не null), и присваивает его переменной c типа Customer внутри if. Это заменяет старую пару var c = obj as Customer; if (c != null). Переменная определённо присвоена только там, где проверка заведомо истинна, поэтому после if (!(obj is Customer c)) return; переменную c можно использовать.
Чем is null отличается от == null в C#?
x is null всегда проверяет саму ссылку. x == null вызывает оператор == типа, если тип его перегружает, что обычно даёт тот же ответ, но не гарантированно. Для противоположной проверки пишите x != null, !(x is null) или x is not null начиная с C# 9.
Как работает switch с шаблонами и when в C#?
Метка case может содержать шаблон плюс условие when: case Order o when o.Total > 100:. Case совпадает, только если совпал шаблон и условие истинно, а case проверяются сверху вниз, поэтому более конкретные ставьте первыми. Компилятор сообщает CS8120, когда case никогда не может быть достигнут, потому что предыдущий уже его покрывает.
В какой версии C# появились какие шаблоны?
C# 7.0: шаблоны типа, констант и var в is и case, плюс when. C# 8: switch-выражения, шаблоны свойств, кортежей и позиционные. C# 9: реляционные (> 5), логические (and, or, not) и голые шаблоны типа. C# 10: расширенные шаблоны свойств ({ Address.City: "Porto" }). C# 11: шаблоны списков ([1, .., var last]).