Сопоставление рекурсивных шаблонов
Заметка
Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Включены предлагаемые изменения спецификации и информация, необходимая во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.
Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия фиксируются в соответствующих встречах по проектированию языка (LDM).
Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .
Проблема чемпиона: https://github.com/dotnet/csharplang/issues/45
Сводка
Расширения сопоставления шаблонов для C# позволяют использовать множество преимуществ алгебраических типов данных и сопоставления шаблонов на функциональных языках, но таким образом, что плавно интегрируется с базовым языком. Элементы этого подхода вдохновлены сопутствующими функциями в языках программирования F# и Scala.
Подробный дизайн
Выражение Is
Оператор is
расширен для тестирования выражения на соответствие с шаблоном .
relational_expression
: is_pattern_expression
;
is_pattern_expression
: relational_expression 'is' pattern
;
Эта форма relational_expression является дополнением к существующим формам в спецификации C#. Это ошибка во время компиляции, если relational_expression слева от маркера is
не назначает значение или не имеет типа.
Каждый идентификатор шаблона представляет новую локальную переменную, определенно назначенную после is
оператора true
(т. е. определенно назначаемой, если задано значение true).
Примечание. Существует техническая неоднозначность между типом в
is-expression
и constant_pattern, любой из которых может быть допустимым анализом квалифицированного идентификатора. Мы пытаемся закрепить его как тип для совместимости с предыдущими версиями языка; только в случае неудачи мы разрешаем его, как это делаем с выражениями в других контекстах, первым найденным элементом (которым должна быть либо константа, либо тип). Эта неоднозначность присутствует только в правой части выраженияis
.
Шаблоны
Шаблоны используются в операторе is_pattern, в switch_statementи в switch_expression для выражения формы данных, с которой сравниваются входящие данные (которые называют входным значением). Шаблоны могут быть рекурсивными, чтобы части данных могли быть сопоставлены с подшаблонами.
pattern
: declaration_pattern
| constant_pattern
| var_pattern
| positional_pattern
| property_pattern
| discard_pattern
;
declaration_pattern
: type simple_designation
;
constant_pattern
: constant_expression
;
var_pattern
: 'var' designation
;
positional_pattern
: type? '(' subpatterns? ')' property_subpattern? simple_designation?
;
subpatterns
: subpattern
| subpattern ',' subpatterns
;
subpattern
: pattern
| identifier ':' pattern
;
property_subpattern
: '{' '}'
| '{' subpatterns ','? '}'
;
property_pattern
: type? property_subpattern simple_designation?
;
simple_designation
: single_variable_designation
| discard_designation
;
discard_pattern
: '_'
;
Шаблон объявления
declaration_pattern
: type simple_designation
;
declaration_pattern одновременно проверяет, что выражение имеет заданный тип и приводит его к такому типу, если проверка успешна. Это может привести к локальной переменной заданного типа, именуемой заданным идентификатором, если обозначение является single_variable_designation. Эта локальная переменная точно присваивается, когда результат операции сопоставления шаблонов true
.
Семантика времени выполнения этого выражения заключается в том, что оно проверяет тип левого операнда relational_expression на соответствие типу в шаблоне. Если это тип среды выполнения (или некоторый подтип), а не null
, результатом is operator
является true
.
Некоторые сочетания статического типа левой стороны и заданного типа считаются несовместимыми и приводят к ошибке во время компиляции. Значение статического типа E
называется совместимым с шаблоном типа T
, если существует преобразование удостоверений, неявное преобразование ссылок, упаковочное преобразование, явное преобразование ссылок или распаковочное преобразование из E
в T
, или если один из этих типов является открытым типом. Это ошибка во время компиляции, если входные данные типа E
не совместимые с шаблоном с типом в шаблоне типа, с которым он соответствует.
Шаблон типа полезен для выполнения проверок типа во время выполнения для ссылочных типов и заменяет идиому.
var v = expr as Type;
if (v != null) { // code using v
С немного более краткой формулировкой
if (expr is Type v) { // code using v
Это ошибка, если тип является типом значения, допускающего значение NULL.
Шаблон типа можно использовать для проверки значений типов, допускающих значение NULL: значение типа Nullable<T>
(или поля T
) соответствует шаблону типа T2 id
, если значение не равно null, а тип T2
— T
, или какой-либо базовый тип или интерфейс T
. Например, в фрагменте кода
int? x = 3;
if (x is int v) { // code using v
Условие инструкции if
true
во время выполнения, а переменная v
содержит значение 3
типа int
внутри блока. После блока переменная v
находится в области видимости, но не обязательно присвоена.
Постоянный шаблон
constant_pattern
: constant_expression
;
Константный шаблон проверяет значение выражения на константное значение. Константой может быть любое константное выражение, например литерал, имя объявленной переменной const
или константой перечисления. Если входное значение не является открытым типом, константное выражение неявно преобразуется в тип соответствующего выражения; Если тип входного значения не совместимый с шаблоном с типом константного выражения, операция сопоставления шаблонов является ошибкой.
Шаблон c считается соответствующим преобразованным входным значением e, если object.Equals(c, e)
вернет true
.
Мы ожидаем, что e is null
станет наиболее распространенным способом проверки null
в недавно написанном коде, так как он не может вызвать определяемый пользователем operator==
.
Шаблон Var
var_pattern
: 'var' designation
;
designation
: simple_designation
| tuple_designation
;
simple_designation
: single_variable_designation
| discard_designation
;
single_variable_designation
: identifier
;
discard_designation
: _
;
tuple_designation
: '(' designations? ')'
;
designations
: designation
| designations ',' designation
;
Если обозначения
var (x, (y, z))
эквивалентен (var x, (var y, var z))
.
Это ошибка, если имя var
привязывается к типу.
Отмена шаблона
discard_pattern
: '_'
;
Выражение e всегда соответствует шаблону _
. Другими словами, каждое выражение соответствует шаблону отбрасывания.
Шаблон отбрасывания не может использоваться в качестве шаблона is_pattern_expression.
Позиционный шаблон
Позиционный шаблон проверяет, что входное значение не null
, вызывает соответствующий метод Deconstruct
и выполняет дальнейшее сопоставление шаблонов в результирующих значениях. Он также поддерживает синтаксис шаблонов, аналогичный кортежу (без указанного типа), если тип входного значения совпадает с типом, содержащим Deconstruct
, или если тип входного значения является типом кортежа, или если тип входного значения object
или ITuple
, а тип среды выполнения выражения реализует ITuple
.
positional_pattern
: type? '(' subpatterns? ')' property_subpattern? simple_designation?
;
subpatterns
: subpattern
| subpattern ',' subpatterns
;
subpattern
: pattern
| identifier ':' pattern
;
Если типа
Учитывая совпадение входного значения с типом шаблона(
subpattern_list)
, метод выбирается путем поиска в типа для объявлений доступных Deconstruct
и выбора одного из них с использованием одинаковых правил, что и для объявления деконструкции.
Это ошибка, если positional_pattern пропускает тип, имеет один подпаттерн без идентификатора, не имеет property_subpattern и не имеет simple_designation. Это проясняет разницу между constant_pattern, заключённой в скобки, и positional_pattern.
Чтобы извлечь значения, соответствующие шаблонам в списке,
- Если тип был опущен, а тип входного значения является кортежем, то число подпаттернов должно совпадать с кардинальностью кортежа. Элемент каждого кортежа сопоставляется с соответствующим подпаттерном, и совпадение считается успешным, если все они завершаются успешно. Если какой-либо подпаттерн имеет идентификатор , то он должен обозначать элемент кортежа на соответствующей позиции в типе кортежа.
- В противном случае, если подходящий
Deconstruct
существует в качестве члена типа, это ошибка во время компиляции, если тип входного значения не совместим по шаблону с типом . Во время выполнения входное значение проверяется на типа. Если это не удается, совпадение позиционного шаблона терпит неудачу. При успешном выполнении входное значение преобразуется в этот тип иDeconstruct
вызывается с помощью свежих переменных компилятора для получения параметровout
. Каждое полученное значение сопоставляется с соответствующим подпаттерном , и совпадение считается успешным, если все они успешны. Если любой подпаттерн имеет идентификатор , то он должен присвоить параметру соответствующее положениеDeconstruct
. - В противном случае, если типа опущен, а входное значение имеет тип
object
илиITuple
, или какой-либо тип, который можно преобразовать вITuple
неявным преобразованием ссылки, и идентификатор не присутствует среди подпаттернов, тогда мы выполняем сопоставление, используяITuple
. - В противном случае шаблон является ошибкой во время компиляции.
Порядок сопоставления подпаттернов во время выполнения не определен, и если сопоставление не удалось, оно может не пытаться изучать все подпаттерны.
Пример
В этом примере используется множество функций, описанных в этой спецификации.
var newState = (GetState(), action, hasKey) switch {
(DoorState.Closed, Action.Open, _) => DoorState.Opened,
(DoorState.Opened, Action.Close, _) => DoorState.Closed,
(DoorState.Closed, Action.Lock, true) => DoorState.Locked,
(DoorState.Locked, Action.Unlock, true) => DoorState.Closed,
(var state, _, _) => state };
Шаблон свойств
Шаблон свойства проверяет, что входное значение не null
и рекурсивно сопоставляет значения, извлеченные с помощью доступных свойств или полей.
property_pattern
: type? property_subpattern simple_designation?
;
property_subpattern
: '{' '}'
| '{' subpatterns ','? '}'
;
Ошибка возникает, если любой подпаттернproperty_pattern не содержит идентификатор (он должен быть второго типа, который имеет идентификатор ). Запятая после последнего подпаттерна является необязательна.
Обратите внимание, что шаблон проверки null выходит из тривиального шаблона свойств. Чтобы проверить, является ли строка s
не null, можно написать любую из следующих форм.
if (s is object o) ... // o is of type object
if (s is string x) ... // x is of type string
if (s is {} x) ... // x is of type string
if (s is {}) ...
При сопоставлении выражения
Во время выполнения выражение проверяется на соответствие T. Если это не проходит, проверка шаблона свойства завершается неудачей, и результат - false
. Если он успешно выполнен, то каждое property_subpattern поле или свойство считывается и его значение соответствует соответствующему шаблону. Результат всего матча false
только в том случае, если результат любого из них будет false
. Порядок сопоставления подпаттернов не указан, и несоответствие может не охватывать все подпаттерны во время выполнения. Если совпадение выполнено успешно и simple_designationproperty_pattern является single_variable_designation, она определяет переменную типа T, назначаемую соответствующим значением.
Примечание. Шаблон свойства можно использовать для сопоставления шаблонов с анонимными типами.
Пример
if (o is string { Length: 5 } s)
Выражение switch
Добавляется switch_expression для поддержки семантики, подобной switch
, в контексте выражения.
Синтаксис языка C# дополнен следующими синтаксическими правилами.
multiplicative_expression
: switch_expression
| multiplicative_expression '*' switch_expression
| multiplicative_expression '/' switch_expression
| multiplicative_expression '%' switch_expression
;
switch_expression
: range_expression 'switch' '{' '}'
| range_expression 'switch' '{' switch_expression_arms ','? '}'
;
switch_expression_arms
: switch_expression_arm
| switch_expression_arms ',' switch_expression_arm
;
switch_expression_arm
: pattern case_guard? '=>' expression
;
case_guard
: 'when' null_coalescing_expression
;
switch_expression не допускается в качестве expression_statement.
Мы планируем смягчить это в будущей редакции.
Тип switch_expression — это наиболее распространенный тип (§12.6.3.15) выражений, отображаемых справа от маркеров =>
switch_expression_arm, если такой тип существует, и выражение в каждой части выражения коммутатора может быть неявно преобразовано в этот тип. Кроме того, мы добавим новое преобразование выражения коммутатора, которое является предопределенным неявным преобразованием из выражения switch к каждому типу T
, для которого существует неявное преобразование из каждого выражения в каждой ветви в T
.
Это ошибка, если некоторые switch_expression_armшаблоны не могут изменить результат, так как некоторые предыдущие шаблоны и условия всегда будут выполнены.
Выражение переключателя, как говорят, исчерпывающим, если часть выражения коммутатора обрабатывает каждое значение его входных данных. Компилятор должен создать предупреждение, если выражение переключателя не исчерпывающим.
В среде выполнения результатом выражения switch_expression является значение выражения первого совпавшего switch_expression_arm, для которого выражение в левой части switch_expression соответствует шаблону switch_expression_arm, и для которого, если присутствует, case_guard из switch_expression_armвычисляется как true
. Если такого switch_expression_armнет, switch_expression выдает экземпляр исключения System.Runtime.CompilerServices.SwitchExpressionException
.
Необязательный синтаксический анализ при переключении на кортежный литерал
Чтобы включить кортежный литерал с помощью switch_statement, необходимо написать лишние скобки.
switch ((a, b))
{
разрешать
switch (a, b)
{
Круглые скобки инструкции switch необязательны, если выражение, по которому выполняется переключение, является литералом кортежа.
Порядок оценки в сопоставлении шаблонов
Благодаря гибкости компилятора при переупорядочении операций, выполняемых во время сопоставления шаблонов, можно обеспечить гибкость, которая может использоваться для повышения эффективности сопоставления шаблонов. Требование (необязательное) заключается в том, что свойства, к которым обращаются в шаблоне, и методы деконструкции должны быть "чистыми" (без побочных эффектов, идемпотентными и т. д.). Это не означает, что мы добавим понятие "чистота" в язык программирования; это лишь значит, что мы предоставляем компилятору больше гибкости в переупорядочении операций.
Резолюция 2018-04-04 LDM: подтверждено: компилятор может менять порядок вызовов Deconstruct
, доступа к свойствам и вызовов методов в ITuple
, а также может предположить, что возвращаемые значения одинаковы из нескольких вызовов. Компилятор не должен вызывать функции, которые не могут повлиять на результат, и мы будем очень осторожны перед внесением изменений в созданный компилятором порядок оценки в будущем.
Некоторые возможные оптимизации
Компиляция процесса сопоставления шаблонов может использовать общие части шаблонов. Например, если тест типа верхнего уровня двух последовательных шаблонов в switch_statement является одинаковым типом, созданный код может пропустить тест типа для второго шаблона.
Если некоторые шаблоны являются целыми числами или строками, компилятор может создать тот же код, который он создает для инструкции switch-statement в более ранних версиях языка.
Дополнительные сведения об этих типах оптимизаций см. в разделе [Скотт и Рамси (2000)].
C# feature specifications