В C# 7 наконец-то появилась долгожданная функция, называемая сопоставлением с образцом ( сопоставление с образцом ).
Если вы знакомы с функциональными языками, такими как F#, возможно, вас немного разочарует эта функция в ее нынешнем виде, но даже сегодня она может упростить ваш код в самых разных сценариях.
Каждая новая функция сопряжена с риском для разработчика, работающего над приложением, критичным к производительности.
Новые уровни абстракции великолепны, но чтобы эффективно их использовать, вам нужно знать, что происходит «под капотом».
Сегодня мы рассмотрим внутреннюю структуру сопоставления с образцом, чтобы понять, как оно реализовано.
В языке C# введена концепция шаблона, который можно использовать в выражении is и внутри блока.
случай оператор выключатель .
Существует 3 типа шаблонов:
- Константа шаблона
- Введите шаблон
- вар-шаблон
Сопоставление шаблонов в выражениях is
Выражение is может проверить, равно ли значение константе, а проверка типа может дополнительно создать переменную шаблона ( переменная шаблона ).public void IsExpressions(object o) { // Alternative way checking for null if (o is null) Console.WriteLine("o is null"); // Const pattern can refer to a constant value const double value = double.NaN; if (o is value) Console.WriteLine("o is value"); // Const pattern can use a string literal if (o is "o") Console.WriteLine("o is \"o\""); // Type pattern if (o is int n) Console.WriteLine(n); // Type pattern and compound expressions if (o is string s && s.Trim() != string.Empty) Console.WriteLine("o is not blank"); }
Я обнаружил несколько интересных аспектов, связанных с сопоставлением шаблонов в выражениях is:
- Переменная, введенная в оператор если , поднимается во внешнюю зону видимости.
- Переменная, введенная в оператор если , полностью определен (определенно назначен) только тогда, когда шаблон соответствует.
- Текущая реализация сопоставления константных шаблонов в выражениях is не очень эффективна.
public void ScopeAndDefiniteAssigning(object o)
{
if (o is string s && s.Length != 0)
{
Console.WriteLine("o is not empty string");
}
// Can't use 's' any more. 's' is already declared in the current scope.
if (o is int n || (o is string s2 && int.TryParse(s2, out n)))
{
Console.WriteLine(n);
}
}
Первый оператор если вводит переменную с , и переменная видна внутри всего метода.
Это разумно, но усложнит логику, если другие операторы if в том же блоке попытаются снова использовать то же имя.
В этом случае вы нужно используйте другое имя, чтобы избежать коллизий.
Переменная, введенная в выражение is, полностью определена только тогда, когда предикат истинный .
Это означает, что переменная н во втором заявлении если не определена в правом операнде, но поскольку эта переменная уже объявлена, мы можем использовать ее как переменную вне в методе int.TryParse .
Третий аспект, упомянутый выше, является наиболее важным.
Рассмотрим следующий код: public void BoxTwice(int n)
{
if (n is 42) Console.WriteLine("n is 42");
}
В большинстве случаев выражение is преобразуется в object.Equals(constValue, переменная) (даже если в спецификации указано, что оператор == следует использовать для примитивных типов): public void BoxTwice(int n)
{
if (object.Equals(42, n))
{
Console.WriteLine("n is 42");
}
}
Этот код вызывает 2 ящика (бокса), которые могут очень серьезно повлиять на производительность, если они используются на критическом пути приложения.
Когда-то выражение о равно нулю также вызвана упаковка (см.
Неоптимальный код для e равен нулю ) и надеюсь, что текущее поведение тоже скоро исправится (вот соответствующий билет на Гитхабе).
Если н -переменная имеет тип объект , Что о — 42 приведет к выделению одной памяти (для упаковки литерала 42 ), хотя такой код на основе переключателей не приводит к выделению памяти.
шаблон var в выражениях
Образец вар является особым случаем шаблона типа с одним ключевым отличием: шаблон будет соответствовать любому значению, даже если это значение нулевой .
public void IsVar(object o)
{
if (o is var x) Console.WriteLine($"x: {x}");
}
o объект верен , Когда о Нет нулевой , Но о - это вар х Всегда истинный .
Компилятор знает об этом и в режиме Release (*) полностью удаляет конструкцию if и просто оставляет вызов консольного метода.
К сожалению, компилятор не предупреждает о недостижимости кода в следующем случае: if (!(o is var x)) Console.WriteLine("Недоступно") .
Надеюсь, это тоже будет исправлено.
(*) Непонятно, почему поведение отличается только в режиме Release. Но я думаю, что все проблемы имеют одну и ту же природу: первоначальная реализация функции неоптимальна.
Но на основании этот комментарий Нил Гафт после этого изменится: «Код сопоставления с плохим шаблоном переписан с нуля (для поддержки рекурсивных шаблонов).
Я ожидаю, что большинство улучшений, которые вы ищете здесь, будут «бесплатными» в новом коде».
Отсутствие проверки на нулевой делает этот случай очень особенным и потенциально опасным.
Но если вы знаете, что именно происходит, эта опция сопоставления может оказаться вам полезной.
Его можно использовать для введения временной переменной в выражение: public void VarPattern(IEnumerable<string> s)
{
if (s.FirstOrDefault(o => o != null) is var v
&& int.TryParse(v, out var n))
{
Console.WriteLine(n);
}
}
Is-выражение и оператор «Эlvis»
Есть еще один случай, который мне показался очень полезным.Шаблон типа соответствует значению, только если это значение не равно нулевой .
Мы можем использовать эту логику «фильтра» с нулевое распространение оператор, чтобы сделать код более читабельным: public void WithNullPropagation(IEnumerable<string> s)
{
if (s?.
FirstOrDefault(str => str.Length > 10)?.
Length is int length) { Console.WriteLine(length); } // Similar to if (s?.
FirstOrDefault(str => str.Length > 10)?.
Length is var length2 && length2 != null) { Console.WriteLine(length2); } // And similar to var length3 = s?.
FirstOrDefault(str => str.Length > 10)?.
Length;
if (length3 != null)
{
Console.WriteLine(length3);
}
}
Обратите внимание, что один и тот же шаблон можно использовать как для типов значений, так и для ссылочных типов.
Сопоставление шаблонов в блоках переключателей
C# 7 расширяет оператор выключатель для использования образцов в случай -блоки: public static int Count<T>(this IEnumerable<T> e)
{
switch (e)
{
case ICollection<T> c: return c.Count;
case IReadOnlyCollection<T> c: return c.Count;
// Matches concurrent collections
case IProducerConsumerCollection<T> pc: return pc.Count;
// Matches if e is not null
case IEnumerable<T> _: return e.Count();
// Default case is handled when e is null
default: return 0;
}
}
В примере показан первый набор изменений в операторе переключения.
- В операторе выключатель можно использовать переменную любого типа.
- Предложение случай может указывать на закономерность.
- Порядок предложений важен случай .
Компилятор выдает ошибку, если предыдущий случай соответствует базовому типу и следующему случай – соответствует производному типу.
- Все случай -блоки содержат неявную проверку на нулевой (**).
В предыдущем примере последний случай -block верен, поскольку он сработает только тогда, когда аргумент не равен нулевой .
отказаться " Имя _ является особенным и сообщает компилятору, что переменная не нужна.
Введите образец в предложении случай требуется имя переменной, и если вы не собираетесь ее использовать, вы можете игнорировать ее с помощью _ .
В следующем фрагменте показана еще одна особенность сопоставления с образцом, основанная на выключатель — возможность использования предикатов: public static void FizzBuzz(object o)
{
switch (o)
{
case string s when s.Contains("Fizz") || s.Contains("Buzz"):
Console.WriteLine(s);
break;
case int n when n % 5 == 0 && n % 3 == 0:
Console.WriteLine("FizzBuzz");
break;
case int n when n % 5 == 0:
Console.WriteLine("Fizz");
break;
case int n when n % 3 == 0:
Console.WriteLine("Buzz");
break;
case int n:
Console.WriteLine(n);
break;
}
}
Выключатель может иметь более одного случай -блок того же типа.
В этом случае компилятор объединяет все проверки типов в один блок, чтобы избежать лишних вычислений: public static void FizzBuzz(object o)
{
// All cases can match only if the value is not null
if (o != null)
{
if (o is string s &&
(s.Contains("Fizz") || s.Contains("Buzz")))
{
Console.WriteLine(s);
return;
}
bool isInt = o is int;
int num = isInt ? ((int)o) : 0;
if (isInt)
{
// The type check and unboxing happens only once per group
if (num % 5 == 0 && num % 3 == 0)
{
Console.WriteLine("FizzBuzz");
return;
}
if (num % 5 == 0)
{
Console.WriteLine("Fizz");
return;
}
if (num % 3 == 0)
{
Console.WriteLine("Buzz");
return;
}
Console.WriteLine(num);
}
}
}
Но нужно иметь в виду две вещи:
- Компилятор объединяет только последовательные случай -блоки одного типа, а если смешивать блоки разных типов, то компилятор будет генерировать неоптимальный код:
switch (o) { // The generated code is less optimal: // If o is int, then more than one type check and unboxing operation // may happen. case int n when n == 1: return 1; case string s when s == "": return 2; case int n when n == 2: return 3; default: return -1; }
Компилятор преобразует его следующим образом:if (o is int n && n == 1) return 1; if (o is string s && s == "") return 2; if (o is int n2 && n2 == 2) return 3; return -1;
- Компилятор делает все возможное, чтобы предотвратить распространенные проблемы нарушения порядка.
случай -блоки
switch (o) { case int n: return 1; // Error: The switch case has already been handled by a previous case. case int n when n == 1: return 2; }
Но компилятор не знает, что один предикат сильнее другого, и, по сути, делает недоступными следующие блоки:switch (o) { case int n when n > 0: return 1; // Will never match, but the compiler won't warn you about it case int n when n > 1: return 2; }
Сопоставление с образцом 101
- В C# 7 были представлены следующие шаблоны: шаблон const, шаблон типа, шаблон var и шаблон отказаться .
- Шаблоны можно использовать в выражениях и в случай блоки
- Реализация шаблона const в выражениях is для типов значений далека от совершенства с точки зрения производительности.
- Шаблон var соответствует любому значению, и с ним следует быть осторожным.
- Оператор выключатель может использоваться для установки проверок типов с дополнительными предикатами в предложениях когда .
NET #сопоставление с образцом #.
NET #C++
-
Легенды И Мемы Ретрочата
19 Oct, 24 -
Вперёд... Или Правила Общения В Команде
19 Oct, 24 -
Плюсы И Минусы Работы В Ночное Время
19 Oct, 24 -
Какой Биржей Вы Пользуетесь?
19 Oct, 24 -
«Твой День» Продвигает Себя В Интернете
19 Oct, 24