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

11.1 Geral

Um padrão é uma forma sintactica que pode ser usada com o is operador (§12.15.12), em um switch_statement (§13.8.3) e em um switch_expression (§12.12) para expressar a forma dos 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
    : logical_pattern
    ;

primary_pattern
    : parenthesized_pattern
    | declaration_pattern
    | constant_pattern
    | var_pattern
    | positional_pattern
    | property_pattern
    | discard_pattern
    | type_pattern
    | relational_pattern
    ;

parenthesized_pattern
    : '(' pattern ')'
    ;

A '(' pattern ')' produção permite que um padrão seja colocado entre parênteses para impor a ordem de avaliação entre os padrões combinados usando um dos logical_patterns.

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 é 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 de tipo is (§12.15.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 é 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 padrão com o tipo T se houver uma conversão de identidade, uma conversão de referência implícita ou explícita, uma conversão de boxe, uma conversão de unboxing ou uma conversão de tipo de valor nulo implícita ou explícita de E para T, ou se 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 (var do formulário, ... ) 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 ossubpadrões{ de tipo} de padrão, 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 seus subpadrões 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ãois 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

Padrão de tipo 11.2.8

Um type_pattern é usado para testar se o valor de entrada padrão (§11.1) tem um determinado tipo.

type_pattern
    : type
    ;

Um padrão de tipo que nomeia um tipo T é aplicável a todos os tipos E para os quais E o padrão é compatível com T (§11.2.2).

O tipo de runtime do valor é testado em relação ao tipo usando as mesmas regras especificadas no operador is-type (§12.15.12.1). Se o teste for bem-sucedido, o padrão corresponderá a esse valor. Será um erro em tempo de compilação se o tipo for um tipo que permite valor nulo. Esse formulário de padrão nunca corresponde a um null valor.

11.2.9 Padrão relacional

Um relational_pattern é usado para testar relacionalmente o valor de entrada padrão (§11.1) em relação a um valor constante.

relational_pattern
    : '<'  relational_expression
    | '<=' relational_expression
    | '>'  relational_expression
    | '>=' relational_expression
    ;

O relational_expression em um relational_pattern é necessário para ser avaliado como um valor constante.

Os padrões relacionais dão suporte aos operadores relacionais<, <=e >=>a todos os tipos internos que dão suporte a esses operadores relacionais binários com ambos os operandos com o mesmo tipo: sbyte, , byte, short, , ushortint, uint, long, , ulong, char, float, , double, decimal, , nint, , nuintenumerações.

Um relational_pattern é aplicável a um tipo T se um operador relacional binário interno adequado for definido com ambos os operandos do tipo Tou se houver uma conversão nula ou de unboxing explícita do T tipo da expressão constante.

É um erro de tempo de compilação se a expressão for avaliada como double.NaN, float.NaNou uma constante nula.

Quando o valor de entrada tem um tipo para o qual um operador relacional binário interno adequado é definido, a avaliação desse operador é tomada como o significado do padrão relacional. Caso contrário, o valor de entrada será convertido no tipo da expressão constante usando uma conversão nula ou de unboxing explícita. Será um erro de tempo de compilação se não houver essa conversão. O padrão é considerado como não corresponder se a conversão falhar. Se a conversão for bem-sucedida, o resultado da operação de correspondência de padrões será o resultado da avaliação da expressão e «op» v em que e está a entrada convertida, «op» é o operador relacional e v é a expressão constante.

Exemplo:

Console.WriteLine(Classify(13));
Console.WriteLine(Classify(double.NaN));
Console.WriteLine(Classify(2.4));

static string Classify(double measurement) => measurement switch
{
    < -4.0 => "Too low",
    > 10.0 => "Too high",
    double.NaN => "Unknown",
    _ => "Acceptable",
};

A saída produzida é

Too high
Unknown
Acceptable

exemplo de fim

11.2.10 Padrão lógico

Um logical_pattern é usado para negar o resultado de uma correspondência de padrão ou para combinar os resultados de várias correspondências de padrão usando conjunção (and) ou disjunção (or).

logical_pattern
    : disjunctive_pattern
    ;

disjunctive_pattern
    : disjunctive_pattern 'or' conjunctive_pattern
    | conjunctive_pattern
    ;

conjunctive_pattern
    : conjunctive_pattern 'and' negated_pattern
    | negated_pattern
    ;

negated_pattern
    : 'not' negated_pattern
    | primary_pattern
    ;

not, ande or são coletivamente chamados de operadores padrão.

Um negated_pattern corresponderá se o padrão que está sendo negado não corresponder e vice-versa. Um conjunctive_pattern requer que ambos os padrões correspondam. Um disjunctive_pattern requer que qualquer padrão corresponda. Ao contrário de seus equivalentes de operador de && idioma e or||and operadores de curto-circuito e não são operadores de curto-circuito.

É um erro de tempo de compilação para que uma variável padrão seja declarada abaixo de um not operador ou or padrão.

Observação: como nem notor pode produzir uma atribuição definitiva para uma variável de padrão, é um erro declarar uma nessas posições. nota final

Em um conjunctive_pattern, o tipo de entrada do segundo padrão é restringido pelos requisitos de restrição de tipo do primeiro padrão do and. O tipo estreito de um padrão P é definido da seguinte maneira:

  • Se P for um padrão de tipo, o tipo estreito será o tipo do tipo do padrão de tipo.
  • Caso contrário, se P for um padrão de declaração, o tipo restrito será o tipo do tipo do padrão de declaração.
  • Caso contrário, se P for um padrão recursivo que fornece um tipo explícito, o tipo restrito será esse tipo.
  • Caso contrário, se P for correspondido por meio das regras de ITuple um positional_pattern (§11.2.5), o tipo restrito será o tipo System.ITuple.
  • Caso contrário, se P for um padrão constante em que a constante não é a constante nula e onde a expressão não tem conversão de expressão constante para o tipo de entrada, o tipo estreito é o tipo da constante.
  • Caso contrário, se P for um padrão relacional em que a expressão constante não tenha conversão de expressão constante para o tipo de entrada, o tipo estreito será o tipo da constante.
  • Caso contrário, se P for um padrão or , o tipo estreito será o tipo comum do tipo estreito dos subpadrões se esse tipo comum existir. Para essa finalidade, o algoritmo de tipo comum considera apenas conversões de identidade, boxe e referência implícita e considera todos os subpadrões de uma sequência de or padrões (ignorando padrões parênteses).
  • Caso contrário, se P for um padrão and , o tipo estreito será o tipo estreito do padrão certo. Além disso, o tipo estreito do padrão esquerdo é o tipo de entrada do padrão direito.
  • Caso contrário, o tipo restrito é Po tipo de P entrada.

Observação: conforme indicado pela gramática, not tem precedência sobre and, que tem precedência sobre or. Isso pode ser explicitamente indicado ou substituído usando parênteses. nota final

Quando um padrão aparece no lado direito de is, a extensão do padrão é determinada pela gramática; como resultado, os operadores andpadrão , ore not dentro do padrão se associam mais fortemente do que os operadores lógicos &&, ||e ! fora do padrão.

Exemplo:

Console.WriteLine(Classify(13));
Console.WriteLine(Classify(-100));
Console.WriteLine(Classify(5.7));

static string Classify(double measurement) => measurement switch
{
    < -40.0 => "Too low",
    >= -40.0 and < 0 => "Low",
    >= 0 and < 10.0 => "Acceptable",
    >= 10.0 and < 20.0 => "High",
    >= 20.0 => "Too high",
    double.NaN => "Unknown",
};

A saída produzida é

High
Too low
Acceptable

exemplo de fim

Exemplo:

Console.WriteLine(GetCalendarSeason(new DateTime(2021, 1, 19)));
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 10, 9)));
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 5, 11)));

static string GetCalendarSeason(DateTime date) => date.Month switch
{
    3 or 4 or 5 => "spring",
    6 or 7 or 8 => "summer",
    9 or 10 or 11 => "autumn",
    12 or 1 or 2 => "winter",
    _ => throw new ArgumentOutOfRangeException(nameof(date),
      $"Date with unexpected month: {date.Month}."),
};

A saída produzida é

winter
autumn
spring

exemplo de fim

Exemplo:

object msg = "msg";
object obj = 5;
bool flag = true;

// This is parsed as: (msg is (not int) or string)
result = msg is not int or string;
Console.WriteLine($"msg (\"msg\"): msg is not int or string: {result}");

// This is parsed as: (obj is (int or string)) && flag
bool result = obj is int or string && flag;
Console.WriteLine($"obj (5), flag (true): obj is int or string && flag: {result}");

// This is parsed as: (obj is int) || ((obj is string) && flag)
result = obj is int || obj is string && flag;
Console.WriteLine($"obj (5), flag (true): obj is int || obj is string && flag: {result}");

flag = false;
// This is parsed as: (obj is (int or string)) && flag
result = obj is int or string && flag;
Console.WriteLine($"obj (5), flag (false): obj is int or string && flag: {result}");

// This is parsed as: (obj is int) || ((obj is string) && flag)
result = obj is int || obj is string && flag;
Console.WriteLine($"obj (5), flag (false): obj is int || obj is string && flag: {result}");

A saída produzida é

msg ("msg"): msg is not int or string: True
obj (5), flag (true): obj is int or string && flag: True
obj (5), flag (true): obj is int || obj is string && flag: True
obj (5), flag (false): obj is int or string && flag: False
obj (5), flag (false): obj is int || obj is string && flag: True

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 qualquer uma das seguintes condições se mantiver:

  • a especificação para o comportamento de runtime desse padrão é que P corresponde K.
  • P é um type_pattern para o tipo T e K não null é e o tipo de K runtime é T ou um tipo derivado ou T um tipo que implementa T.
  • P é um relational_pattern com o operador «op» e constante v, e a expressão K «op» v seria avaliada como true.
  • P é um negated_patternnot P₁ e P₁ não corresponderia K.
  • P é um conjunctive_patternP₁ and P₂ e ambos P₁ corresponderiam K e P₂ corresponderiam K.
  • P é um disjunctive_patternP₁ or P₂ e P₁ corresponderia K ou P₂ corresponderia K.
  • P é um discard_pattern.

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
  • 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).
  • P é um type_pattern para o tipo T e o conjunto de padrões Q é exaustivo para o tipo T.
  • P é um relational_pattern com o operador «op» e o valor vconstante e, para cada valor do tipo de entrada que satisfaz a relação «op» v, algum padrão no conjunto Q corresponderia a esse valor.
  • Pé um disjunctive_patternP₁ or P₂ e o conjunto de subsumes P₁ e Q subsumes P₂de padrõesQ.
  • P é um conjunctive_patternP₁ and P₂ e pelo menos um dos seguintes retenções: Q subsumes P₁ou Q subsumes P₂.
  • P é um negated_patternnot P₁ e Q é exaustivo para o tipo de entrada considerando apenas os valores não correspondidos por P₁.
  • P é um discard_pattern e o conjunto de padrões Q é exaustivo para o tipo do valor de entrada padrão e o valor de entrada padrão não é de um tipo anulável ou algum padrão em Q corresponderia null.
  • Algum padrão é Q um disjunctive_patternQ₁ or Q₂ e substituir esse padrão Q₁ pelo in Q produziria um conjunto que subsume Pou substituí-lo Q₂ produziria um conjunto que subsume P.
  • Algum padrão é Q um negated_patternnot Q₁ e P não corresponderia a nenhum valor que Q₁ correspondesse.

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 é 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; ou
  4. Algum padrão é Q um type_pattern 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; ou
  5. Algum padrão é Q um discard_pattern; ou
  6. Os padrões incluem Q uma combinação de relational_patterne constant_patterncujos intervalos abrangem coletivamente todos os valores possíveis do Ttipo subjacente não anulável. Para float e double tipos, isso inclui System.Double.NaN ou System.Single.NaN , respectivamente, uma vez NaN que não é correspondido por nenhum padrão relacional; ou
  7. Algum padrão é Q um disjunctive_patternP₁ or P₂ e substituir esse padrão por ambos P₁ e P₂ em Q gera um conjunto que é exaustivo para T; ou
  8. Algum padrão é Q um negated_patternnot P₁ e os padrões em Q conjunto com os valores não correspondidos P₁ abrangem todos os valores possíveis de T. Um negated_patternnot P₁ é exaustivo por si só quando P₁ não corresponde a nenhum valor possível de T; ou
  9. Algum padrão é Q um conjunctive_patternP₁ and P₂, e o conjunto que contém apenas P₁ é exaustivo e T o conjunto que contém apenas P₂ é exaustivo para T.

Observação: quando um padrão de tipo inclui tipos anuláveis, o padrão pode ser exaustivo para o tipo, mas ainda gerar um aviso porque o padrão de tipo não corresponderá a um null valor. nota final

Observação: para tipos de ponto flutuante, a combinação de padrões < 0 e >= 0não é exaustiva porque nenhum padrão relacional corresponde NaN. Um conjunto exaustivo correto seria < 0, >= 0e double.NaN (ou float.NaN). nota final

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