Partilhar via


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

11.1 Generalidades

Um padrão é uma forma sintática que pode ser usada com o is operador (§12.12.12) e em um switch_statement (§13.8.3) para expressar a forma dos dados com os quais os dados recebidos devem ser comparados. Um padrão é testado em relação à expressão de uma instrução switch ou contra um relational_expression que está no lado esquerdo de um is operador, cada um dos quais é referido como um valor de entrada de padrão.

11.2 Formas padrão

11.2.1 Generalidades

Um padrão pode ter uma das seguintes formas:

pattern
    : declaration_pattern
    | constant_pattern
    | var_pattern
    ;

Um declaration_pattern e um var_pattern podem resultar na declaração de uma variável local.

Cada formulário de padrão define o conjunto de tipos de 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 aparece 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 em tempo de compilação porque o tipo de tempo de compilação de v é TextReader. Uma variável do tipo TextReader nunca pode ter um valor que seja compatível com a 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 em tempo de compilação porque o tipo de tempo de compilação é vobject. Uma variável do tipo object pode ter um valor compatível com a referência com string:

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

Exemplo final

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.

11.2.2 Modelo da declaração

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

declaration_pattern
    : type simple_designation
    ;
simple_designation
    : single_variable_designation
    ;
single_variable_designation
    : identifier
    ;

O tipo de tempo de execução do valor é testado em relação ao tipo no padrão usando as mesmas regras especificadas no operador is-type (§12.12.12.1). Se o teste for bem-sucedido, o padrão corresponderá a esse valor. É um erro em tempo de compilação se o tipo for um tipo de valor anulável (§8.3.12). Este formulário de padrão nunca corresponde a um null valor.

Nota: 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 é um tipo anulável. Nota final

Dado um valor de entrada padrão (§11.1) e, se o simple_designation é o identificador _, ele denota um descarte (§9.2.9.2) e o valor de e não está vinculado 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.) Se simple_designation for qualquer outro identificador, é introduzida uma variável local (§9.2.9.1) do tipo dado nomeado pelo identificador fornecido. A essa variável local é atribuído o valor do valor de entrada do padrão quando o padrão corresponde ao valor.

Certas combinações do tipo estático do valor de entrada do padrão e do tipo dado são consideradas incompatíveis e resultam em um erro em tempo de compilação. Diz-se que um valor de tipo E estático é 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 boxe ou uma conversão de unboxing de E para T, ou se um ou ET for um tipo aberto (§8.4.3). Um padrão de declaração nomeando um tipo T é aplicável aT.

Nota: O suporte para tipos abertos pode ser mais útil ao verificar tipos que podem ser struct ou tipos de classe, e o boxe deve ser evitado. Nota final

Exemplo: O padrão de declaração é útil para executar testes de tipo em 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 final

É um erro se type for um tipo de valor anulável.

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 in a box) Tcorresponde a um padrão T2 id de tipo se o valor for não-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 mantém o valor 3 do tipo int dentro do bloco. Exemplo final

11.2.3 Padrão constante

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

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 enum, o valor constante do padrão convertido para esse 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 um tipo de enum, o valor constante do padrão convertido para seu tipo subjacente; caso contrário,
  • O valor do valor constante do padrão.

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

  • se e tem tipo integral ou tipo enum, ou uma forma anulável de um destes, e v tem tipo integral, o padrão Pcorresponde ao valor e se o resultado da expressão e == v for true;
  • 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 maiúsculas e minúsculas.

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 final

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
    ;

Dado um valor de entrada padrão (§11.1) e, se a denominação é o identificador _, isso denota um descarte (§9.2.9.2), e o valor de e não está vinculado 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.) Se designação for qualquer outro identificador, no tempo de execução o valor de e está vinculado a uma variável local recém-introduzida (§9.2.9.1) desse nome cujo tipo é o tipo estático de e, e o valor de entrada do padrão é atribuído a essa variável local.

É um erro se o nome var se ligar a um tipo onde um var_pattern é usado.

11.3 Subsunção do padrão

Em uma instrução switch, é um erro se o padrão de um caso é subsumido 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 subsume um determinado padrão:

Um padrão P corresponderiaK

Um conjunto de padrões Qsubsume um padrão P se qualquer uma das seguintes condições se mantiver:

  • Pé um padrão constante e qualquer um dos padrões no conjunto Q corresponderia Pao valor convertido do
  • Pé um padrão var e o conjunto de padrões Q é exaustivonull
  • 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 se mantiver:

  1. T é um tipo integral ou enum, ou uma versão anulável de um desses, e para cada valor possível de T's tipo subjacente não anulável, algum padrão em Q corresponderia a esse valor;
  2. Algum padrão em Q é um padrão var;
  3. Alguns padrões 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 boxe 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 final