Разбираем Сопоставления по Шаблону в C#. Часть 2/5
Часть 1
Оператор
is - основа сопоставления по шаблонуЛюбой шаблон можно проверить с помощью выражения
is, и результатом будет логическое значение (bool).Сопоставление по шаблону — не магия, а всего лишь синтаксический сахар. Компилятор C# преобразует шаблоны в обычный IL-код, похожий на тот, который генерировался бы из обычных выражений. Поэтому сопоставление по шаблону не медленнее, чем эквивалентные булевы выражения, и компилятор иногда будет переписывать шаблон более агрессивно, чем когда переписывает обычную цепочку
||.Проверка на null
Простейший шаблон проверки на null:
static void NullPattern(object o, int? ni) {
if (o is null) Console.WriteLine("o is null");
if (!(ni is null)) Console.WriteLine(ni.Value);
}С C# 9 доступна отрицательная форма
is not null, и это рекомендуемый способ вместо != null:if (obj is not null) { … } //C# 9Шаблон лучше, чем
== null, потому что оператор == может быть перегружен, и существуют классы, в которых оператор == возвращает true для экземпляра, сравниваемого с null (например, UnityEngine.Object в Unity). Шаблон сравнения всегда представляет собой сравнение по ссылке.Сопоставление литеральных значений
Шаблон константы сравнивает значение с константой времени компиляции:
static void ConstantPattern(double d, string str) {
if (d is Math.PI) Console.WriteLine("d - Пи");
if (str is "314159") Console.WriteLine("str похоже на Пи");
}Константой считаются:
- целые или числа с плавающей запятой,
- символьные и строковые литералы,
- логические значения true и false,
- значения перечислений,
- имя объявленного поля const или null.
Внимание! Значение, заложенное в шаблон, должно быть константой времени компиляции. Если оно находится в файле appsettings.json, в БД или в статическом поле только для чтения, инициализируемом при запуске, вы не сможете использовать его в шаблоне. Команда C# пока не ослабила это ограничение.
Шаблон типа
Проверяет, имеет ли выражение заданный тип, и в случае успеха связывает значение с новой переменной этого типа. Он заменяет старый алгоритм проверки типа с приведением типов:
static void TypePattern(Shape shape) {
if (shape is Circle c1)
Console.WriteLine($"фигура – круг радиуса {c1.Radius}");
if (shape is Rectangle r && r.Width == r.Height)
Console.WriteLine($"фигура – квадрат со стороной {r.Width}");
}Шаблон типа оправдывает себя в операторах switch для типов, не имеющих общего базового класса. BCL полон таких ситуаций. Вот небольшой пример: подсчёт элементов произвольной последовательности за O(1), если возможно:
public static int Count<T>(this IEnumerable<T> seq) {
switch (seq) {
case Array a: return a.Length;
case ICollection<T> c: return c.Count;
case IReadOnlyCollection<T> c: return c.Count;
case IEnumerable<T> _: return seq.Count(); // не null
default: return 0; // null
}
}Порядок имеет значение. Компилятор отклонит операторы case, если обнаружит их недостижимость, например, если ICollection<T> будет перед List<T>.
Шаблон определения и шаблон типа
В спецификации языка C# это два разных шаблона.
1. Шаблон определения
shape is Circle c проверяет тип во время выполнения и объявляет новую переменную c этого типа.2. Шаблон типа
shape is Circle проверяет только тип и ничего не объявляет. До C#9 для этого случая приходилось писать Circle _.Оба варианта отклоняют null. Это свойство делает шаблон объявления самым чистым способом проверки типа значения, допускающего null, и его преобразования в базовый тип за один шаг:
int? m = 12;
if (m is int num) // совпадает при m.HasValue истинном
Console.WriteLine($"m имеет значение {num}");
Оператор when
Внутри оператора
switch ключевое слово when добавляет произвольное логическое условие поверх шаблона. Так можно обойти ограничение на константы времени компиляции:public static int CountUpTo10<T>(this IEnumerable<T> seq) {
switch (seq) {
case Array a when a.Length <= 10:
return a.Length;
case ICollection<T> c when c.Count <= 10:
return c.Count;
case IReadOnlyCollection<T> c when c.Count <= 10:
return c.Count;
default:
throw new ArgumentException("Слишком большой набор");
}
}When работает только в switch и не допускается в if, даже в последних версиях C#:
if (seq is Array a when a.Length <= 10) // ошибка компиляции
Продолжение следует…
Источник: https://blog.ndepend.com/c-pattern-matching-explained/