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 tipoTextReader
nunca pode ter um valor que seja compatível com a referência comstring
: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 é
v
object
. Uma variável do tipoobject
pode ter um valor compatível com a referência comstring
: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ãoe is T _
de declaração são equivalentes quandoT
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 E
T
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)T
corresponde a um padrãoT2 id
de tipo se o valor for não-nulo eT2
forT
, ou algum tipo base ou interface deT
. Por exemplo, no fragmento de códigoint? 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ávelv
mantém o valor3
do tipoint
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 P
constante , 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
P
corresponde ao valor e se o resultado da expressãoe == v
fortrue
; - O padrão
P
corresponde ao valor e seobject.Equals(e, v)
retornatrue
.
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 Q
subsume 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 conjuntoQ
corresponderiaP
ao valor convertido do -
P
é um padrão var e o conjunto de padrõesQ
é exaustivonull
-
P
é um padrão de declaração com tipoT
e o conjunto de padrõesQ
é exaustivo para o tipoT
(§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:
-
T
é um tipo integral ou enum, ou uma versão anulável de um desses, e para cada valor possível deT
's tipo subjacente não anulável, algum padrão emQ
corresponderia a esse valor; - Algum padrão em
Q
é um padrão var; - Alguns padrões em
Q
é um padrão de declaração para o tipoD
, e há uma conversão de identidade, uma conversão de referência implícita ou uma conversão de boxe deT
paraD
.
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
ECMA C# draft specification