Поделиться через


11 Шаблонов и сопоставления шаблонов

11.1 Общие

Шаблон — это синтаксическая форма, которую можно использовать с is оператором (§12.14.12), в switch_statement (§13.8.3) и в switch_expression (§12.11) для выражения формы данных, с которой сравниваются входящие данные. Шаблоны могут быть рекурсивными, чтобы части данных могли быть сопоставлены с вложенными шаблонами.

Шаблон проверяется на соответствие значению в ряде контекстов:

  • В инструкции switch шаблон метки регистра проверяется в выражении инструкции switch.
  • В операторе is-patternшаблон справа проверяется с выражением слева.
  • В выражении коммутатора шаблонswitch_expression_arm проверяется на выражение в левой части выражения switch-expression.
  • В вложенных контекстах вложенный шаблон проверяется на значения, полученные из свойств, полей или индексированных из других входных значений в зависимости от формы шаблона.

Значение, по которому проверяется шаблон, называется входным значением шаблона.

Формы шаблонов 11.2

11.2.1 Общие

Шаблон может иметь одну из следующих форм:

pattern
    : declaration_pattern
    | constant_pattern
    | var_pattern
    | positional_pattern
    | property_pattern
    | discard_pattern
    ;

Некоторые шаблонымогут привести к объявлению локальной переменной.

Каждая форма шаблона определяет набор типов входных значений, к которым может применяться шаблон. Шаблон P применяется к типу T , если T относится к типам, значения которых могут соответствовать шаблону. Это ошибка во время компиляции, если шаблон отображается в программе для сопоставления входного значения шаблона P (§11.1) типа T , если P к нему неприменимо T.

Пример. В следующем примере возникает ошибка во время компиляции, так как тип времени компиляции v имеет значение TextReader. Переменная типа TextReader никогда не может иметь значение, совместимое со ссылкой:string

TextReader v = Console.In; // compile-time type of 'v' is 'TextReader'
if (v is string) // compile-time error
{
    // code assuming v is a string
}

Однако следующее не создает ошибку во время компиляции, так как тип времени компиляции v имеет значение object. Переменная типа object может иметь значение, совместимое со ссылкой:string

object v = Console.In;
if (v is string s)
{
    // code assuming v is a string
}

пример конца

Каждая форма шаблона определяет набор значений, для которых шаблон соответствует значению во время выполнения.

Порядок оценки операций и побочных эффектов во время сопоставления шаблонов (вызовы Deconstruct, доступ к свойствам и вызовы методов в System.ITuple) не указаны.

Шаблон объявления 11.2.2

Declaration_pattern используется для проверки того, что значение имеет заданный тип и, если тест выполнен успешно, при необходимости укажите значение в переменной этого типа.

declaration_pattern
    : type simple_designation
    ;
simple_designation
    : discard_designation
    | single_variable_designation
    ;
discard_designation
    : '_'
    ;
single_variable_designation
    : identifier
    ;

Simple_designation с маркером _ следует рассматривать как discard_designation, а не single_variable_designation.

Тип среды выполнения значения проверяется на тип в шаблоне, используя те же правила, указанные в операторе is-type (§12.14.12.12.1). Если тест выполнен успешно, шаблон соответствует указанному значению. Это ошибка во время компиляции, если тип является типом значения null (§8.3.12) или типом ссылки, допускающей значение NULL (§8.9.3). Эта форма шаблона никогда не соответствует значению null .

Примечание. Выражение e is T is-type и шаблон e is T _ объявления эквивалентны, если T не является пустым. конечная заметка

Учитывая входное значение шаблона (§11.1) e, если simple_designationdiscard_designation, он обозначает отмену (§9.2.9.2), а значение e не привязано ни к чему. (Хотя объявленная переменная с именем _ может находиться в области в этой точке, эта именованной переменной не рассматривается в этом контексте.) В противном случае, если simple_designation single_variable_designation, вводятся локальная переменная (§9.2.9) заданного типа, именуемого заданным идентификатором. Эта локальная переменная назначается значение входного значения шаблона, если шаблон соответствует значению.

Некоторые сочетания статического типа входного значения шаблона и заданного типа считаются несовместимыми и приводят к ошибке во время компиляции. Значение статического типаE, как утверждается, совместимо с типомT, если существует преобразование удостоверений, неявное или явное преобразование ссылок, преобразование бокса или распаковка преобразования из ET, или если ET или является открытым типом (§8.4.3). Шаблон объявления, именующий тип T , применим к каждому типу E , для которого E совместим Tшаблон.

Примечание. Поддержка открытых типов может оказаться наиболее полезной при проверке типов, которые могут быть либо структурой, либо типами классов, а бокс следует избежать. конечная заметка

Пример. Шаблон объявления полезен для выполнения тестов типов времени выполнения ссылочных типов и заменяет идиом

var v = expr as Type;
if (v != null) { /* code using v */ }

с немного более кратким

if (expr is Type v) { /* code using v */ }

пример конца

Пример. Шаблон объявления можно использовать для проверки значений типов, допускающих значение 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 находится в области, но не определенно назначена. пример конца

11.2.3 Константный шаблон

Constant_pattern используется для проверки значения входного значения шаблона (§11.1) с заданным константным значением.

constant_pattern
    : constant_expression
    ;

Шаблон константы применим к типу, если существует неявное преобразование из константного P

Для константного шаблона Pпреобразованное значение равно

  • Если тип входного значения шаблона является целочисленным или типом перечисления, значение константы шаблона преобразуется в этот тип; иначе
  • Если тип входного значения шаблона — это версия целочисленного типа или типа перечисления, константное значение шаблона, преобразованное в его базовый тип; иначе
  • значение константы шаблона.

Учитывая входное значение шаблона e и константный шаблон P с преобразованным значением v,

  • Значение e, если имеет целочисленный тип или тип перечисления, или пустую форму одного из них, и v имеет целочисленный тип, шаблон P соответствует значению e, если результат выражения e == v имеет trueзначение ; в противном случае
  • Шаблон P соответствует значению e, если возвращаетсяobject.Equals(e, v).true

Пример. Оператор switch в следующем методе использует пять постоянных шаблонов в метках регистра.

static decimal GetGroupTicketPrice(int visitorCount)
{
    switch (visitorCount) 
    {
        case 1: return 12.0m;
        case 2: return 20.0m;
        case 3: return 27.0m;
        case 4: return 32.0m;
        case 0: return 0.0m;
        default: throw new ArgumentException(...);
    }
}

пример конца

Шаблон Var 11.2.4

Var_pattern соответствует каждому значению. То есть операция сопоставления шаблонов с var_pattern всегда выполняется успешно.

Var_pattern применимо к каждому типу.

var_pattern
    : 'var' designation
    ;
designation
    : simple_designation
    | tuple_designation
    ;
tuple_designation
    : '(' designations? ')'
    ;
designations
    : designation (',' designation)*
    ;

Учитывая входное значение шаблона (§11.1) e, если обозначениеdiscard_designation, оно обозначает отмену (§9.2.9.2), а значение e не привязано к чему-либо. (Хотя объявленная переменная с таким именем может находиться в области в этой точке, эта именованной переменной не рассматривается в этом контексте.) В противном случае, если присвоениеsingle_variable_designation, во время выполнения значение e привязано к недавно введенной локальной переменной (§9.2.9) этого имени, тип которого является статическим типом e, а входное значение шаблона назначается этой локальной переменной.

Это ошибка, если имя var привязывается к типу, в котором используется var_pattern .

Если обозначение является tuple_designation, шаблон эквивалентен positional_pattern (§11.2.5) обозначения формы(var, ... ) где обозначениянаходятся в tuple_designation. Например, шаблон var (x, (y, z)) эквивалентен (var x, (var y, var z)).

11.2.5 Позиционный шаблон

Positional_pattern проверяет, не nullявляется ли входное значение, вызывает соответствующий Deconstruct метод (§12.7) и выполняет дальнейшее сопоставление шаблонов в результирующих значениях. Он также поддерживает синтаксис шаблонов, аналогичный кортежам (без предоставленного типа), если тип входного значения совпадает с типом, содержащим Deconstruct, или если тип входного значения является типом кортежа, или если тип входного значения является object или System.ITuple типом среды выполнения выражения System.ITuple.

positional_pattern
    : type? '(' subpatterns? ')' property_subpattern? simple_designation?
    ;
subpatterns
    : subpattern (',' subpattern)*
    ;
subpattern
    : pattern
    | identifier ':' pattern
    ;

Учитывая совпадение входного значения сподпаттернами)типа( шаблона, метод выбирается путем поиска в типе объявлений для доступных Deconstruct объявлений и выбора одного из них с использованием одних и того же правила, что и для объявления деконструкции. Это ошибка, если positional_pattern окупает тип, имеет один подпаттерн без идентификатора, не имеет property_subpattern и не имеет simple_designation. Это отличается от constant_pattern , которое скобок и positional_pattern. Чтобы извлечь значения, соответствующие шаблонам в списке,

  • Если тип опущен, а тип входного выражения является типом кортежа, число подпаттернов должно совпадать с кратностью кортежа. Каждый элемент кортежа сопоставляется с соответствующим подпаттерном, и совпадение завершается успешно, если все эти элементы успешно выполнены. Если в подпаттерне есть идентификатор, то он должен назвать элемент кортежа в соответствующей позиции в типе кортежа.
  • В противном случае, если подходящее Deconstruct значение существует в качестве члена типа, это ошибка во время компиляции, если тип входного значения не совместим с типом. Во время выполнения входное значение проверяется на тип. Если это не удается, то совпадение позиционного шаблона завершается ошибкой. При успешном выполнении входное значение преобразуется в этот тип и Deconstruct вызывается с новыми переменными, созданными компилятором, для получения выходных параметров. Каждое полученное значение сопоставляется с соответствующим подпаттерном, и совпадение завершается успешно, если все эти значения успешно выполнены. Если какой-либо подпаттерн имеет идентификатор, то он должен присвоить параметру соответствующее положение Deconstruct.
  • В противном случае, если тип опущен, и входное значение имеет тип object или какой-либо тип, который можно преобразовать в System.ITuple неявное преобразование ссылок, и идентификатор не отображается среди подпаттернов, то совпадение используется System.ITuple.
  • В противном случае шаблон является ошибкой во время компиляции.

Порядок сопоставления подпаттернов во время выполнения не определен, и неудачное совпадение может не пытаться соответствовать всем подпаттернам.

Пример. Здесь мы деконструируем результат выражения и сопоставляем полученные значения с соответствующими вложенными шаблонами:

static string Classify(Point point) => point switch
{
    (0, 0) => "Origin",
    (1, 0) => "positive X basis end",
    (0, 1) => "positive Y basis end",
    _ => "Just a point",
};

public readonly struct Point
{
    public int X { get; }
    public int Y { get; }
    public Point(int x, int y) => (X, Y) = (x, y);
    public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}

пример конца

Пример. Имена элементов кортежа и деконструкционных параметров можно использовать в позиционной схеме следующим образом:

var numbers = new List<int> { 10, 20, 30 };
if (SumAndCount(numbers) is (Sum: var sum, Count: var count))
{
    Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}");
}

static (double Sum, int Count) SumAndCount(IEnumerable<int> numbers)
{
    int sum = 0;
    int count = 0;
    foreach (int number in numbers)
    {
        sum += number;
        count++;
    }
    return (sum, count);
}

Выходные данные создаются

Sum of [10 20 30] is 60

пример конца

Шаблон свойств 11.2.6

Property_pattern проверяет, не nullсоответствует ли входное значение и рекурсивно сопоставляет значения, извлеченные с помощью доступных свойств или полей.

property_pattern
    : type? property_subpattern simple_designation?
    ;
property_subpattern
    : '{' '}'
    | '{' subpatterns ','? '}'
    ;

Это ошибка, если какой-либо подпаттернproperty_pattern не содержит идентификатор.

Это ошибка во время компиляции, если тип является типом значения null (§8.3.12) или типом ссылки, допускающей значение NULL (§8.9.3).

Примечание. Шаблон проверки null выходит из тривиального шаблона свойства. Чтобы проверить, является ли строка s не null, можно написать любую из следующих форм:

#nullable enable
string s = "abc";
if (s is object o) ...  // o is of type object
if (s is string x1) ... // x1 is of type string
if (s is {} x2) ...     // x2 is of type string
if (s is {}) ...

конечная заметка Учитывая совпадение выражения e с типом{ шаблонаproperty_pattern_list}, это ошибка во время компиляции, если выражение e не совместимо с типом T, указанным по типу. Если тип отсутствует, предполагается, что тип является статическим типом e. Каждый идентификатор, отображаемый в левой части property_pattern_list, должен назначить доступное для чтения свойство или поле T. Если simple_designation property_pattern присутствует, он объявляет переменную шаблона типа T.

Во время выполнения выражение проверяется на T. Если это не удается, совпадение шаблона свойства завершается ошибкой, и результатом является false. В случае успешного выполнения каждое property_subpattern поле или свойство считывается, а его значение соответствует соответствующему шаблону. Результат всего совпадения заключается false только в том случае, если результат любого из них false. Порядок сопоставления подпаттернов не указан, и не удалось проверить все подпаттерны во время выполнения. Если совпадение выполнено успешно, и simple_designationproperty_pattern является single_variable_designation, объявленная переменная назначается соответствующее значение.

Property_pattern можно использовать для сопоставления шаблонов с анонимными типами.

Пример:

var o = ...;
if (o is string { Length: 5 } s) ...

пример конца

Пример: проверка типа выполнения и объявление переменной можно добавить в шаблон свойств следующим образом:

Console.WriteLine(TakeFive("Hello, world!"));  // output: Hello
Console.WriteLine(TakeFive("Hi!"));            // output: Hi!
Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6', '7' }));  // output: 12345
Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' }));  // output: abc

static string TakeFive(object input) => input switch
{
    string { Length: >= 5 } s => s.Substring(0, 5),
    string s => s,
    ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()),
    ICollection<char> symbols => new string(symbols.ToArray()),
    null => throw new ArgumentNullException(nameof(input)),
    _ => throw new ArgumentException("Not supported input type."),
};

Выходные данные создаются

Hello
Hi!
12345
abc

пример конца

Шаблон отмены 11.2.7

Каждое выражение соответствует шаблону отмены, что приводит к отмене значения выражения.

discard_pattern
    : '_'
    ;

Это ошибка во время компиляции для использования шаблона отмены в relational_expression формы relational_expressionisили в качестве шаблона switch_label.

Примечание. В этих случаях для сопоставления любого выражения используйте var_pattern с отменой var _. конечная заметка

Пример:

Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday));
Console.WriteLine(GetDiscountInPercent(null));
Console.WriteLine(GetDiscountInPercent((DayOfWeek)10));

static decimal GetDiscountInPercent(DayOfWeek? dayOfWeek) => dayOfWeek switch
{
    DayOfWeek.Monday => 0.5m,
    DayOfWeek.Tuesday => 12.5m,
    DayOfWeek.Wednesday => 7.5m,
    DayOfWeek.Thursday => 12.5m,
    DayOfWeek.Friday => 5.0m,
    DayOfWeek.Saturday => 2.5m,
    DayOfWeek.Sunday => 2.0m,
    _ => 0.0m,
};

Выходные данные создаются

5.0
0.0
0.0

Здесь для обработки null и целочисленного значения, не имеющих соответствующего DayOfWeek члена перечисления, используется шаблон отмены. Это гарантирует, что switch выражение обрабатывает все возможные входные значения. пример конца

11.3 Подсемпция шаблона

В инструкции switch это ошибка, если шаблон дела подмечен предыдущим набором незащищенных случаев (§13.8.3). В неофициальном порядке это означает, что любое входное значение было бы сопоставлено одним из предыдущих случаев. Следующие правила определяют, когда набор шаблонов подмножет заданный шаблон:

Шаблон Pбудет соответствовать константеK, если спецификация для поведения среды выполнения этого шаблона соответствуетPK.

Набор шаблонов Qподменимирует шаблон P , если какое-либо из следующих условий хранится:

  • P— это константный шаблон, и любой из шаблонов в наборе Q будет соответствовать Pпреобразованном значению.
  • P— это шаблон var, и набор шаблонов Q является исчерпывающим.
  • P— это шаблон объявления с типом, и набор шаблонов T является Q для типа T (§11.4).

11.4. Исчерпывающая схема

Неформальным образом набор шаблонов является исчерпывающим для типа, если для каждого возможного значения этого типа, отличного от NULL, применяется некоторый шаблон в наборе. Следующие правила определяют, когда набор шаблонов является исчерпывающим для типа:

Набор шаблонов Q является исчерпывающим:

  1. T является целочисленным или типом перечисления или версией, допускающей значение NULL, и для каждого из возможных значений Tбазового типа, не допускающего значения NULL, некоторые шаблоны будут Q соответствовать значению этого значения; или
  2. Некоторые шаблоны в Q шаблоне var; или
  3. Некоторые шаблоны в Q шаблоне

Пример:

static void M(byte b)
{
    switch (b) {
        case 0: case 1: case 2: ... // handle every specific value of byte
            break;
        // error: the pattern 'byte other' is subsumed by the (exhaustive)
        // previous cases
        case byte other: 
            break;
    }
}

пример конца