Compartilhar via


11 Padrões e correspondência de padrões

11.1 Geral

Um padrão é um formulário sintactico que pode ser usado com o is operador (§12.14.12), em um switch_statement (§13.8.3) e em um switch_expression (§12.11) para expressar a forma de dados em relação aos quais os dados de entrada devem ser comparados. Os padrões podem ser recursivos, de modo que partes dos dados possam ser correspondidas com sub padrões.

Um padrão é testado em relação a um valor em vários contextos:

  • Em uma instrução switch, o padrão de um rótulo de caso é testado em relação à expressão da instrução switch.
  • Em um operador is-pattern , o padrão no lado direito é testado em relação à expressão à esquerda.
  • Em uma expressão switch, o padrão de um switch_expression_arm é testado em relação à expressão no lado esquerdo da expressão de comutador.
  • Em contextos aninhados, o sub padrão é testado em relação aos valores recuperados de propriedades, campos ou indexados de outros valores de entrada, dependendo do formulário padrão.

O valor no qual um padrão é testado é chamado de valor de entrada padrão.

11.2 Formas de padrão

11.2.1 Geral

Um padrão pode ter uma das seguintes formas:

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

Alguns padrõespodem resultar na declaração de uma variável local.

Cada formulário de padrão define o conjunto de tipos para valores de entrada aos quais o padrão pode ser aplicado. Um padrão P é aplicável a um tipo T se T estiver entre os tipos cujos valores o padrão pode corresponder. É um erro em tempo de compilação se um padrão P aparecer em um programa para corresponder a um valor de entrada de padrão (§11.1) do tipo T se P não for aplicável a T.

Exemplo: o exemplo a seguir gera um erro de tempo de compilação porque o tipo de tempo de v compilação de é TextReader. Uma variável do tipo TextReader nunca pode ter um valor compatível com referência com 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
}

No entanto, o seguinte não gera um erro de tempo de compilação porque o tipo de tempo de v compilação de é object. Uma variável do tipo object pode ter um valor compatível com referência com string:

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

exemplo de fim

Cada formulário de padrão define o conjunto de valores para os quais o padrão corresponde ao valor em tempo de execução.

A ordem de avaliação de operações e efeitos colaterais durante a correspondência de padrões (chamadas para Deconstruct, acessos de propriedade e invocações de métodos em System.ITuple) não é especificada.

11.2.2 Padrão de declaração

Um declaration_pattern é usado para testar se um valor tem um determinado tipo e, se o teste for bem-sucedido, fornecer opcionalmente o valor em uma variável desse tipo.

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

Um simple_designation com o token _ deve ser considerado um discard_designation em vez de um single_variable_designation.

O tipo de runtime do valor é testado em relação ao tipo no padrão usando as mesmas regras especificadas no operador is-type (§12.14.12.1). Se o teste for bem-sucedido, o padrão corresponderá a esse valor. Será um erro de tempo de compilação se o tipo for um tipo de valor anulável (§8.3.12) ou um tipo de referência anulável (§8.9.3). Esse formulário de padrão nunca corresponde a um null valor.

Observação: a expressão e is T is-type e o padrão e is T _ de declaração são equivalentes quando T não são um tipo anulável. nota final

Dado um valor de entrada padrão (§11.1) e, se o simple_designation for discard_designation, ele indicará um descarte (§9.2.9.2) e o valor de e não estará associado a nada. (Embora uma variável declarada com o nome _ possa estar no escopo nesse ponto, essa variável nomeada não é vista neste contexto.) Caso contrário, se o simple_designation for single_variable_designation, uma variável local (§9.2.9) do tipo determinado nomeado pelo identificador especificado será introduzida. Essa variável local recebe o valor do valor de entrada do padrão quando o padrão corresponde ao valor.

Determinadas combinações de tipo estático do valor de entrada do padrão e do tipo fornecido são consideradas incompatíveis e resultam em um erro em tempo de compilação. Um valor de tipo E estático é considerado compatível com o tipo T se existir uma conversão de identidade, uma conversão de referência implícita ou explícita, uma conversão de boxing ou uma conversão de unboxing de E para T, ou se ou ET for um tipo aberto (§8.4.3). Um padrão de declaração que nomeia um tipo T é aplicável a.

Observação: o suporte para tipos abertos pode ser mais útil ao verificar tipos que podem ser tipos struct ou class, e a conversão boxing deve ser evitada. nota final

Exemplo: o padrão de declaração é útil para executar testes de tipo de tempo de execução de tipos de referência e substitui o idioma

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

com o ligeiramente mais conciso

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

exemplo de fim

Exemplo: o padrão de declaração pode ser usado para testar valores de tipos anuláveis: um valor de tipo Nullable<T> (ou um boxed T) corresponde a um padrão T2 id de tipo se o valor não for nulo e T2 for T, ou algum tipo base ou interface de T. Por exemplo, no fragmento de código

int? x = 3;
if (x is int v) { /* code using v */ }

A condição da if instrução está true em tempo de execução e a variável v contém o valor 3 do tipo int dentro do bloco. Depois do bloco, a variável v está no escopo, mas não é atribuída definitivamente. exemplo de fim

11.2.3 Padrão constante

Um constant_pattern é usado para testar o valor de um valor de entrada de padrão (§11.1) em relação ao valor constante fornecido.

constant_pattern
    : constant_expression
    ;

Um padrão P constante é aplicável a um tipo T se houver uma conversão implícita da expressão constante de P para o tipo T.

Para um padrão Pconstante , seu valor convertido é

  • Se o tipo do valor de entrada do padrão for um tipo integral ou um tipo de enumeração, o valor constante do padrão será convertido nesse tipo; caso contrário
  • Se o tipo do valor de entrada do padrão for a versão anulável de um tipo integral ou de um tipo de enumeração, o valor constante do padrão será convertido em seu tipo subjacente; caso contrário
  • o valor do valor constante do padrão.

Dado um valor de entrada de padrão e e um padrão P constante com valor convertido v,

  • Se E tiver tipo integral ou tipo enumeração, ou uma forma anulável de um desses, e V tiver tipo integral, o padrão Pcorresponderá ao valor E se o resultado da expressão e == v for true; caso contrário
  • O padrão Pcorresponde ao valor e se object.Equals(e, v) retorna true.

Exemplo: a switch instrução no método a seguir usa cinco padrões constantes em seus rótulos de caso.

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(...);
    }
}

exemplo de fim

11.2.4 Padrão Var

Um var_patterncorresponde a todos os valores. Ou seja, uma operação de correspondência de padrões com um var_pattern sempre é bem-sucedida.

Um var_pattern é aplicável a todos os tipos.

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

Dado um valor de entrada padrão (§11.1) e, se a designação for discard_designation, ele indicará um descarte (§9.2.9.2) e o valor de e não estará associado a nada. (Embora uma variável declarada com esse nome possa estar no escopo nesse ponto, essa variável nomeada não é vista neste contexto.) Caso contrário, se a designação for single_variable_designation, em runtime, o valor de e será associado a uma variável local recém-introduzida (§9.2.9) desse nome cujo tipo é o tipo estático de e e o valor de entrada padrão será atribuído a essa variável local.

É um erro se o nome var for associado a um tipo em que um var_pattern é usado.

Se a designação for um tuple_designation, o padrão será equivalente a um positional_pattern (§11.2.5) da designação do formulário(var, ... ) em que as designaçõessão aquelas encontradas no tuple_designation. Por exemplo, o padrão var (x, (y, z)) é equivalente a (var x, (var y, var z)).

Padrão posicional 11.2.5

Um positional_pattern verifica se o valor de entrada não nullé, invoca um método apropriado Deconstruct (§12.7) e executa uma correspondência de padrões adicional nos valores resultantes. Ele também dá suporte a uma sintaxe de padrão semelhante a tupla (sem o tipo que está sendo fornecido) quando o tipo do valor de entrada é o mesmo que o tipo que contém Deconstruct, ou se o tipo do valor de entrada é um tipo de tupla ou se o tipo do valor de entrada é object ou System.ITuple e o tipo de runtime da expressão implementa System.ITuple.

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

Considerando uma correspondência de um valor de entrada para ossubpadrões) de tipo( de padrão, um método é selecionado pesquisando no tipo declarações acessíveis e Deconstruct selecionando uma entre elas usando as mesmas regras da declaração de desconstrução. É um erro se um positional_pattern omitir o tipo, tiver um único subpadrão sem um identificador, não tiver property_subpattern e não tiver simple_designation. Isso desambigua entre uma constant_pattern parêntese e uma positional_pattern. Para extrair os valores a serem correspondentes aos padrões na lista,

  • Se o tipo for omitido e o tipo da expressão de entrada for um tipo de tupla, o número de subpadrões deverá ser o mesmo que a cardinalidade da tupla. Cada elemento de tupla é correspondido com o subpadrão correspondente e a correspondência é bem-sucedida se todas elas forem bem-sucedidas. Se qualquer subpadrão tiver um identificador, isso nomeará um elemento de tupla na posição correspondente no tipo de tupla.
  • Caso contrário, se houver um adequado Deconstruct como um membro do tipo, será um erro de tempo de compilação se o tipo do valor de entrada não for compatível com o tipo. Em runtime, o valor de entrada é testado em relação ao tipo. Se isso falhar, a correspondência de padrão posicional falhará. Se for bem-sucedido, o valor de entrada será convertido nesse tipo e Deconstruct será invocado com variáveis geradas pelo compilador para receber os parâmetros de saída. Cada valor recebido é correspondido com o subpadrão correspondente e a correspondência é bem-sucedida se todas elas forem bem-sucedidas. Se qualquer subpadrão tiver um identificador, isso nomeará um parâmetro na posição correspondente de Deconstruct.
  • Caso contrário, se o tipo for omitido e o valor de entrada for de tipo object ou algum tipo que possa ser convertido System.ITuple por uma conversão de referência implícita e nenhum identificador aparecer entre os subpadrões, a correspondência usará System.ITuple.
  • Caso contrário, o padrão é um erro de tempo de compilação.

A ordem na qual os subpadrões são correspondidos no runtime não é especificada e uma correspondência com falha pode não tentar corresponder a todos os subpadrões.

Exemplo: aqui, desconstruimos um resultado de expressão e correspondemos aos valores resultantes com os padrões aninhados correspondentes:

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);
}

exemplo de fim

Exemplo: os nomes de elementos de tupla e parâmetros de desconstrução podem ser usados em um padrão posicional, da seguinte maneira:

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);
}

A saída produzida é

Sum of [10 20 30] is 60

exemplo de fim

Padrão de propriedade 11.2.6

Um property_pattern verifica se o valor de entrada não nullé e corresponde recursivamente aos valores extraídos pelo uso de propriedades ou campos acessíveis.

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

É um erro se qualquer subpadrão de um property_pattern não contiver um identificador.

Será um erro de tempo de compilação se o tipo for um tipo de valor anulável (§8.3.12) ou um tipo de referência anulável (§8.9.3).

Observação: um padrão de verificação nula sai de um padrão de propriedade trivial. Para verificar se a cadeia s de caracteres não é nula, é possível gravar qualquer um dos seguintes formulários:

#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 {}) ...

nota final Dada uma correspondência de uma expressão e com o tipo{ de padrãoproperty_pattern_list}, será um erro de tempo de compilação se a expressão e não for compatível com padrão com o tipo T designado por tipo. Se o tipo estiver ausente, o tipo será considerado o tipo estático de e. Cada um dos identificadores que aparecem no lado esquerdo de sua property_pattern_list deve designar uma propriedade legível acessível ou um campo de T. Se o simple_designation do property_pattern estiver presente, ele declara uma variável padrão do tipo T.

Em runtime, a expressão é testada em relação a T. Se isso falhar, a correspondência do padrão de propriedade falhará e o resultado será false. Se for bem-sucedido, cada property_subpattern campo ou propriedade será lido e seu valor corresponderá ao padrão correspondente. O resultado de toda a correspondência será false somente se o resultado de qualquer um deles for false. A ordem na qual os subpadrões são correspondidos não é especificada e uma correspondência com falha pode não testar todos os subpadrões em runtime. Se a correspondência for bem-sucedida e o simple_designation do property_pattern for um single_variable_designation, a variável declarada será atribuída ao valor correspondente.

O property_pattern pode ser usado para correspondência de padrões com tipos anônimos.

Exemplo:

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

exemplo de fim

Exemplo: uma verificação de tipo de tempo de execução e uma declaração de variável podem ser adicionadas a um padrão de propriedade, da seguinte maneira:

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."),
};

A saída produzida é

Hello
Hi!
12345
abc

exemplo de fim

Padrão de descarte 11.2.7

Cada expressão corresponde ao padrão de descarte, o que resulta no valor da expressão sendo descartada.

discard_pattern
    : '_'
    ;

É um erro de tempo de compilação usar um padrão de descarte em um relational_expression dopadrãode relational_expressionis de formulário ou como o padrão de um switch_label.

Observação: nesses casos, para corresponder a qualquer expressão, use um var_pattern com um descarte var _. nota final

Exemplo:

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,
};

A saída produzida é

5.0
0.0
0.0

Aqui, um padrão de descarte é usado para manipular null e qualquer valor inteiro que não tenha o membro correspondente da DayOfWeek enumeração. Isso garante que a switch expressão manipule todos os valores de entrada possíveis. exemplo de fim

11.3 Subsunção de padrão

Em uma instrução switch, é um erro se o padrão de um caso for incluído pelo conjunto anterior de casos não protegidos (§13.8.3). Informalmente, isso significa que qualquer valor de entrada teria sido correspondido por um dos casos anteriores. As regras a seguir definem quando um conjunto de padrões inclui um determinado padrão:

Um padrão Pcorresponderia a uma constante K se a especificação para o comportamento de tempo de execução desse padrão fosse que P correspondesse a K.

Um conjunto de padrões Qinclui um padrão P se qualquer uma das seguintes condições for válida:

  • Pé um padrão constante e qualquer um dos padrões no conjunto Q corresponderia Pao valor convertido de
  • P é um padrão var e o conjunto de padrões Q é exaustivo (§11.4) para o tipo do valor de entrada do padrão (§11.1), e o valor de entrada do padrão não é de um tipo anulável ou algum padrão em Q corresponderia a null.
  • P é um padrão de declaração com tipo T e o conjunto de padrões Q é exaustivo para o tipo T (§11.4).

11.4 Exaustividade do padrão

Informalmente, um conjunto de padrões é exaustivo para um tipo se, para cada valor possível desse tipo diferente de nulo, algum padrão no conjunto for aplicável. As regras a seguir definem quando um conjunto de padrões é exaustivo para um tipo:

Um conjunto de padrões Q é exaustivo para um tipo T se qualquer uma das seguintes condições for válida:

  1. T é um tipo integral ou enumeração, ou uma versão anulável de um desses, e para cada valor possível do Ttipo subjacente não anulável de , algum padrão em Q corresponderia a esse valor; ou
  2. Algum padrão em Q é um padrão var; ou
  3. Algum padrão em Q é um padrão de declaração para o tipo D, e há uma conversão de identidade, uma conversão de referência implícita ou uma conversão de boxing de T para D.

Exemplo:

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;
    }
}

exemplo de fim