Correspondência de padrões - as is expressões e switch operadores , ande or em notpadrões

Use a is expressão, a instrução switch e a expressão switch para corresponder uma expressão de entrada com qualquer número de características. O C# suporta vários padrões, incluindo declaração, tipo, constante, relacional, propriedade, lista, var e descarte. Pode combinar padrões usando as palavras-chave andda lógica booleana , or, e not.

A referência da linguagem C# documenta a versão mais recentemente lançada da linguagem C#. Contém também documentação inicial para funcionalidades em versões preliminares públicas para a próxima versão da linguagem.

A documentação identifica qualquer funcionalidade introduzida pela primeira vez nas últimas três versões da língua ou em pré-visualizações públicas atuais.

Gorjeta

Para saber quando uma funcionalidade foi introduzida pela primeira vez em C#, consulte o artigo sobre o histórico de versões da linguagem C#.

As seguintes expressões e instruções em C# oferecem suporte à correspondência de padrões:

Nessas construções, você pode fazer a correspondência de uma expressão de entrada com qualquer um dos seguintes padrões:

  • Padrão de declaração: verifique o tipo de tempo de execução de uma expressão e, se uma correspondência for bem-sucedida, atribua um resultado de expressão a uma variável declarada.
  • Padrão de tipo: verifique o tipo de tempo de execução de uma expressão.
  • Padrão constante: teste se um resultado de expressão é igual a uma constante especificada.
  • Padrões relacionais: compare um resultado de expressão com uma constante especificada.
  • Padrões lógicos: teste se uma expressão corresponde a uma combinação lógica de padrões.
  • Padrão de propriedade: teste se as propriedades ou campos de uma expressão correspondem a padrões aninhados.
  • Padrão posicional: desconstrua um resultado de expressão e teste se os valores resultantes correspondem a padrões aninhados.
  • var pattern: corresponda a qualquer expressão e atribua seu resultado a uma variável declarada.
  • Descartar padrão: corresponda a qualquer expressão.
  • Padrões de lista: teste se uma sequência de elementos corresponde aos padrões aninhados correspondentes.

Padrões lógicos, de propriedade, posicionais e de lista são padrões recursivos . Ou seja, eles podem conter padrões aninhados .

Para um exemplo de como usar esses padrões para construir um algoritmo orientado por dados, veja Tutorial: Use pattern matching to build drivers algorithms baseados em tipos e dados.

Declaração e padrões de tipo

Use padrões de declaração e tipo para verificar se o tipo de execução de uma expressão é compatível com um dado tipo. Ao usar um padrão de declaração, também pode declarar uma nova variável local. Quando um padrão de declaração corresponde a uma expressão, atribui a variável ao resultado da expressão convertida, como mostra o exemplo seguinte:

object greeting = "Hello, World!";
if (greeting is string message)
{
    Console.WriteLine(message.ToLower());  // output: hello, world!
}

Um padrão de declaração com tipo T corresponde a uma expressão quando um resultado de expressão não é nulo e qualquer uma das seguintes condições é verdadeira:

  • O tipo de tempo de execução de um resultado de expressão tem uma conversão de identidade para T.
  • O tipo T é um ref struct tipo e há uma conversão de identidade da expressão para T.
  • O tipo de tempo de execução de um resultado de expressão deriva do tipo T, implementa a interface Tou outra conversão de referência implícita existe dele para T. Esta condição abrange relações de herança e implementações de interfaces. O exemplo a seguir demonstra dois casos em que essa condição é verdadeira:
    var numbers = new int[] { 10, 20, 30 };
    Console.WriteLine(GetSourceLabel(numbers));  // output: 1
    
    var letters = new List<char> { 'a', 'b', 'c', 'd' };
    Console.WriteLine(GetSourceLabel(letters));  // output: 2
    
    static int GetSourceLabel<T>(IEnumerable<T> source) => source switch
    {
        Array array => 1,
        ICollection<T> collection => 2,
        _ => 3,
    };
    
    No exemplo anterior, na primeira chamada para o GetSourceLabel método, o primeiro padrão corresponde a um valor de argumento porque o tipo int[] de tempo de execução do argumento deriva do Array tipo. Na segunda chamada para o GetSourceLabel método, o tipo List<T> de tempo de execução do argumento não deriva do Array tipo, mas implementa a ICollection<T> interface.
  • O tipo de tempo de execução de um resultado de expressão é um tipo de valor anulável com o tipo T subjacente e o Nullable<T>.HasValue é true.
  • Existe uma conversão de boxing ou unboxing do tipo de tempo de execução de um resultado de expressão para tipo T quando a expressão não é uma instância de um ref struct.

Os padrões de declaração não consideram conversões definidas pelo usuário ou conversões de extensão implícitas.

O exemplo a seguir demonstra as duas últimas condições:

int? xNullable = 7;
int y = 23;
object yBoxed = y;
if (xNullable is int a && yBoxed is int b)
{
    Console.WriteLine(a + b);  // output: 30
}

Para verificar apenas o tipo de expressão, use um descarte _ em vez do nome de uma variável, como mostra o exemplo seguinte:

public abstract class Vehicle {}
public class Car : Vehicle {}
public class Truck : Vehicle {}

public static class TollCalculator
{
    public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
    {
        Car _ => 2.00m,
        Truck _ => 7.50m,
        null => throw new ArgumentNullException(nameof(vehicle)),
        _ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
    };
}

Para esse fim, use um padrão tipográfico, como mostra o exemplo seguinte:

public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
{
    Car => 2.00m,
    Truck => 7.50m,
    null => throw new ArgumentNullException(nameof(vehicle)),
    _ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
};

Como um padrão de declaração, um padrão de tipo corresponde a uma expressão quando um resultado de expressão é não-nulo e seu tipo de tempo de execução satisfaz qualquer uma das condições anteriores.

Para verificar se não é nulo, use umpadrão de constantenull, como mostra o exemplo seguinte:

if (input is not null)
{
    // ...
}

Para mais informações, consulte as secções de padrões de Declaração e Padrão de Tipo da especificação da linguagem C#.

Padrão constante

O padrão constante é uma sintaxe alternativa para == quando o operando certo é uma constante. Use um padrão constante para testar se um resultado de expressão é igual a uma constante especificada, como mostra o exemplo seguinte:

public static decimal GetGroupTicketPrice(int visitorCount) => visitorCount switch
{
    1 => 12.0m,
    2 => 20.0m,
    3 => 27.0m,
    4 => 32.0m,
    0 => 0.0m,
    _ => throw new ArgumentException($"Not supported number of visitors: {visitorCount}", nameof(visitorCount)),
};

Em um padrão constante, você pode usar qualquer expressão constante, como:

A expressão deve ser um tipo convertível para o tipo constante, com uma exceção: uma expressão cujo tipo é Span<char> ou ReadOnlySpan<char> pode ser comparado com cadeias constantes.

Use um padrão constante para verificar nullo , como mostra o exemplo a seguir:

if (input is null)
{
    return;
}

O compilador garante que não invoca um operador == de igualdade sobrecarregado pelo utilizador ao avaliar a expressão x is null.

Você pode usar um padrão constante negadonull para verificar se não é nulo, como mostra o exemplo a seguir:

if (input is not null)
{
    // ...
}

Para obter mais informações, consulte a seção Padrão constante da nota de proposta de recurso.

Padrões relacionais

Use um padrão relacional para comparar um resultado de expressão com uma constante, como mostra o exemplo seguinte:

Console.WriteLine(Classify(13));  // output: Too high
Console.WriteLine(Classify(double.NaN));  // output: Unknown
Console.WriteLine(Classify(2.4));  // output: Acceptable

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

Num padrão relacional, use qualquer um dos operadores <, >, <=, ou >=. A parte direita de um padrão relacional deve ser uma expressão constante. A expressão constante pode ser do tipo inteiro, ponto flutuante, char ou enum .

Para verificar se um resultado de expressão está em um determinado intervalo, faça a correspondência com um and conjuntivo, como mostra o exemplo a seguir:

Console.WriteLine(GetCalendarSeason(new DateTime(2021, 3, 14)));  // output: spring
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 7, 19)));  // output: summer
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 2, 17)));  // output: winter

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

Se um resultado de expressão for null ou não converter para o tipo de uma constante usando uma conversão anulável ou unboxing, um padrão relacional não corresponde à expressão.

Para mais informações, consulte a secção Padrões Relacionais da especificação da linguagem C#.

Padrões lógicos

Use os notcombinadores de padrões , and, e or para criar os seguintes padrões lógicos:

  • Padrão denot negação que corresponde a uma expressão quando o padrão negado não corresponde à expressão. O exemplo a seguir mostra como você pode negar um padrão constantenull para verificar se uma expressão não é nula:

    if (input is not null)
    {
        // ...
    }
    
  • Padrão conjuntivoand que corresponde a uma expressão quando ambos os padrões correspondem à expressão. O exemplo a seguir mostra como você pode combinar padrões relacionais para verificar se um valor está em um determinado intervalo:

    Console.WriteLine(Classify(13));  // output: High
    Console.WriteLine(Classify(-100));  // output: Too low
    Console.WriteLine(Classify(5.7));  // output: Acceptable
    
    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",
    };
    
  • Padrão disjuntivoor que corresponde a uma expressão quando qualquer um dos padrões corresponde à expressão, como mostra o exemplo a seguir:

    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 1, 19)));  // output: winter
    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 10, 9)));  // output: autumn
    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 5, 11)));  // output: spring
    
    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}."),
    };
    

Como mostra o exemplo anterior, pode usar os combinadores de padrões repetidamente num padrão.

Precedência e ordem de controlo

Os combinadores de padrões verificam as expressões nesta ordem, com base na ordem de ligação das expressões:

  • not
  • and
  • or

O not padrão liga-se primeiro ao seu operando. O and padrão liga-se após qualquer not ligação de expressão de padrão. O or padrão liga-se, afinal not , e and os padrões ligam-se aos operandos. O exemplo seguinte tenta associar todos os caracteres que não são minúsculas, a através de z. Ele tem um erro, porque o not padrão se liga antes do and padrão:

// Incorrect pattern. `not` binds before `and`
static bool IsNotLowerCaseLetter(char c) => c is not >= 'a' and <= 'z';

A associação padrão significa que o exemplo anterior é analisado como o exemplo a seguir:

// The default binding without parentheses is shows in this method. `not` binds before `and`
static bool IsNotLowerCaseLetterDefaultBinding(char c) => c is ((not >= 'a') and <= 'z');

Para corrigir o erro, especifique que quer que o not padrão se associe à >= 'a' and <= 'z' expressão:

// Correct pattern. Force `and` before `not`
static bool IsNotLowerCaseLetterParentheses(char c) => c is not (>= 'a' and <= 'z');

Adicionar parênteses torna-se mais importante à medida que os seus padrões se tornam mais complicados. Em geral, use parênteses para esclarecer seus padrões para outros desenvolvedores, como mostra o exemplo a seguir:

static bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');

Nota

A ordem em que o compilador verifica padrões que têm a mesma ordem de ligação é indefinida. Em tempo de execução, o compilador pode verificar primeiro os padrões aninhados à direita de múltiplos or padrões e múltiplos and padrões.

Para mais informações, consulte a secção Combinadores de Padrões da especificação da linguagem C#.

Padrão de propriedade

Use um padrão de propriedades para comparar as propriedades ou campos de uma expressão com padrões aninhados, como mostra o exemplo seguinte:

static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };

Um padrão de propriedades corresponde a uma expressão quando o resultado da expressão não é nulo e cada padrão aninhado corresponde à propriedade ou campo correspondente do resultado da expressão.

Pode adicionar uma verificação de tipo em tempo de execução e uma declaração de variável a um padrão de propriedade, como mostra o exemplo seguinte:

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

Este construto significa especificamente que o padrão is { } corresponde a tudo o que não é nulo, e pode usá-lo em vez de is not null para criar uma variável: somethingPossiblyNull is { } somethingDefinitelyNotNull.

if (GetSomeNullableStringValue() is { } nonNullValue) // Empty property pattern with variable creation
{
    Console.WriteLine("NotNull:" + nonNullValue);
}
else
{
    nonNullValue = "NullFallback"; // we can access the variable here.
    Console.WriteLine("it was null, here's the fallback: " + nonNullValue);
}

Um padrão de propriedade é um padrão recursivo. Você pode usar qualquer padrão como um padrão aninhado. Use um padrão de propriedade para fazer a correspondência entre partes de dados e padrões aninhados, como mostra o exemplo a seguir:

public record Point(int X, int Y);
public record Segment(Point Start, Point End);

static bool IsAnyEndOnXAxis(Segment segment) =>
    segment is { Start: { Y: 0 } } or { End: { Y: 0 } };

O exemplo anterior usa o combinador de padrões e os ortipos de registro.

Você pode fazer referência a propriedades ou campos aninhados dentro de um padrão de propriedade. Esse recurso é conhecido como um padrão de propriedade estendida. Por exemplo, você pode refatorar o método do exemplo anterior no seguinte código equivalente:

static bool IsAnyEndOnXAxis(Segment segment) =>
    segment is { Start.Y: 0 } or { End.Y: 0 };

Para mais informações, consulte a secção Padrão de Propriedades do padrão C#.

Gorjeta

Para melhorar a legibilidade do código, utilize a regra do estilo Simplificar padrão de propriedades (IDE0170). Sugere locais para usar padrões de propriedades estendidos.

Padrão posicional

Use um padrão posicional para desconstruir uma expressão e comparar os valores resultantes com os padrões aninhados correspondentes, como mostra o exemplo seguinte:

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

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

No exemplo anterior, o tipo de uma expressão contém o método de Desconstrução , que o padrão usa para desconstruir um resultado de expressão.

Importante

A ordem dos membros em um padrão posicional deve corresponder à Deconstruct ordem dos parâmetros no método. O código gerado para o padrão posicional chama o Deconstruct método.

Você também pode combinar expressões de tipos de tupla com padrões posicionais. Ao usar esta abordagem, pode comparar múltiplos inputs com vários padrões, como mostra o exemplo seguinte:

static decimal GetGroupTicketPriceDiscount(int groupSize, DateTime visitDate)
    => (groupSize, visitDate.DayOfWeek) switch
    {
        (<= 0, _) => throw new ArgumentException("Group size must be positive."),
        (_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m,
        (>= 5 and < 10, DayOfWeek.Monday) => 20.0m,
        (>= 10, DayOfWeek.Monday) => 30.0m,
        (>= 5 and < 10, _) => 12.0m,
        (>= 10, _) => 15.0m,
        _ => 0.0m,
    };

O exemplo anterior usa padrões relacionais e lógicos .

Você pode usar os nomes de elementos e Deconstruct parâmetros de tupla em um padrão posicional, como mostra o exemplo a seguir:

var numbers = new List<int> { 1, 2, 3 };
if (SumAndCount(numbers) is (Sum: var sum, Count: > 0))
{
    Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}");  // output: Sum of [1 2 3] is 6
}

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

Você também pode estender um padrão posicional de qualquer uma das seguintes maneiras:

  • Adicione uma verificação de tipo em tempo de execução e uma declaração de variável, como mostra o exemplo a seguir:

    public record Point2D(int X, int Y);
    public record Point3D(int X, int Y, int Z);
    
    static string PrintIfAllCoordinatesArePositive(object point) => point switch
    {
        Point2D (> 0, > 0) p => p.ToString(),
        Point3D (> 0, > 0, > 0) p => p.ToString(),
        _ => string.Empty,
    };
    

    O exemplo anterior usa registros posicionais que implicitamente fornecem o Deconstruct método.

  • Use um padrão de propriedade dentro de um padrão posicional, como mostra o exemplo a seguir:

    public record WeightedPoint(int X, int Y)
    {
        public double Weight { get; set; }
    }
    
    static bool IsInDomain(WeightedPoint point) => point is (>= 0, >= 0) { Weight: >= 0.0 };
    
  • Combine os dois usos anteriores, como mostra o exemplo seguinte:

    if (input is WeightedPoint (> 0, > 0) { Weight: > 0.0 } p)
    {
        // ..
    }
    

Um padrão posicional é um padrão recursivo. Ou seja, você pode usar qualquer padrão como um padrão aninhado.

Para obter mais informações, consulte a seção Padrão posicional da nota de proposta de recurso.

var padrão

Use um var padrão para corresponder a qualquer expressão, incluindo null, e atribua o seu resultado a uma nova variável local, como mostra o exemplo seguinte:

static bool IsAcceptable(int id, int absLimit) =>
    SimulateDataFetch(id) is var results 
    && results.Min() >= -absLimit 
    && results.Max() <= absLimit;

static int[] SimulateDataFetch(int id)
{
    var rand = new Random();
    return Enumerable
               .Range(start: 0, count: 5)
               .Select(s => rand.Next(minValue: -10, maxValue: 11))
               .ToArray();
}

Um var padrão é útil quando você precisa de uma variável temporária dentro de uma expressão booleana para manter o resultado de cálculos intermediários. Você também pode usar um var padrão quando precisar executar mais verificações em when protetores de caso de uma switch expressão ou instrução, como mostra o exemplo a seguir:

public record Point(int X, int Y);

static Point Transform(Point point) => point switch
{
    var (x, y) when x < y => new Point(-x, y),
    var (x, y) when x > y => new Point(x, -y),
    var (x, y) => new Point(x, y),
};

static void TestTransform()
{
    Console.WriteLine(Transform(new Point(1, 2)));  // output: Point { X = -1, Y = 2 }
    Console.WriteLine(Transform(new Point(5, 2)));  // output: Point { X = 5, Y = -2 }
}

No exemplo anterior, o padrão var (x, y) é equivalente a um padrão(var x, var y)posicional.

Num var padrão, o tipo de uma variável declarada é o tipo em tempo de compilação da expressão que o padrão corresponde.

Para obter mais informações, consulte a seção Padrão Var da nota de proposta de recurso.

Padrão de descarte

Use um padrão _ para corresponder a qualquer expressão, incluindo null, como mostra o exemplo seguinte:

Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday));  // output: 5.0
Console.WriteLine(GetDiscountInPercent(null));  // output: 0.0
Console.WriteLine(GetDiscountInPercent((DayOfWeek)10));  // output: 0.0

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

No exemplo anterior, um padrão de descarte manipula null e qualquer valor inteiro que não tenha o membro correspondente da DayOfWeek enumeração. Essa garantia garante que uma switch expressão no exemplo gere todos os valores de entrada possíveis. Se você não usar um padrão de descarte em uma switch expressão e nenhum dos padrões da expressão corresponder a uma entrada, o tempo de execução lançará uma exceção. O compilador gera um aviso se uma switch expressão não manipular todos os valores de entrada possíveis.

Um padrão de descarte não pode ser um padrão em uma is expressão ou instrução switch . Nesses casos, para corresponder a qualquer expressão, use um var padrão com um descarte: var _. Um padrão de descarte pode ser um padrão em uma switch expressão.

Para obter mais informações, consulte a seção Descartar padrão da nota de proposta de recurso.

Padrão entre parênteses

Você pode colocar parênteses em torno de qualquer padrão. Normalmente, faz-se isto para enfatizar ou alterar a precedência nos padrões lógicos, como mostra o exemplo seguinte:

if (input is not (float or double))
{
    return;
}

Padrões de lista

Pode comparar um array ou uma lista com uma sequência de padrões, como mostra o exemplo seguinte:

int[] numbers = { 1, 2, 3 };

Console.WriteLine(numbers is [1, 2, 3]);  // True
Console.WriteLine(numbers is [1, 2, 4]);  // False
Console.WriteLine(numbers is [1, 2, 3, 4]);  // False
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3]);  // True

Como mostra o exemplo anterior, um padrão de lista corresponde ao momento em que cada padrão aninhado corresponde ao elemento correspondente de uma sequência de entrada. Você pode usar qualquer padrão dentro de um padrão de lista. Para corresponder a qualquer elemento, use o padrão de descarte ou, se também quiser capturar o elemento, use o padrão var, como mostra o exemplo seguinte:

List<int> numbers = new() { 1, 2, 3 };

if (numbers is [var first, _, _])
{
    Console.WriteLine($"The first element of a three-item list is {first}.");
}
// Output:
// The first element of a three-item list is 1.

Os exemplos anteriores correspondem a uma sequência de entrada inteira com um padrão de lista. Para corresponder elementos apenas no início ou no fim – ou ambos – de uma sequência de entrada, use o padrão .., como mostra o exemplo seguinte:

Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]);  // True
Console.WriteLine(new[] { 1, 1 } is [_, _, ..]);  // True
Console.WriteLine(new[] { 0, 1, 2, 3, 4 } is [> 0, > 0, ..]);  // False
Console.WriteLine(new[] { 1 } is [1, 2, ..]);  // False

Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]);  // True
Console.WriteLine(new[] { 2, 4 } is [.., > 0, 2, 4]);  // False
Console.WriteLine(new[] { 2, 4 } is [.., 2, 4]);  // True

Console.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]);  // True
Console.WriteLine(new[] { 1, 0, 0, 1 } is [1, 0, .., 0, 1]);  // True
Console.WriteLine(new[] { 1, 0, 1 } is [1, 0, .., 0, 1]);  // False

Um padrão de fatia corresponde a zero ou mais elementos. Você pode usar no máximo um padrão de fatia em um padrão de lista. O padrão de fatia só pode aparecer em um padrão de lista.

Você também pode aninhar um subpadrão dentro de um padrão de fatia, como mostra o exemplo a seguir:

void MatchMessage(string message)
{
    var result = message is ['a' or 'A', .. var s, 'a' or 'A']
        ? $"Message {message} matches; inner part is {s}."
        : $"Message {message} doesn't match.";
    Console.WriteLine(result);
}

MatchMessage("aBBA");  // output: Message aBBA matches; inner part is BB.
MatchMessage("apron");  // output: Message apron doesn't match.

void Validate(int[] numbers)
{
    var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0] ? "valid" : "not valid";
    Console.WriteLine(result);
}

Validate(new[] { -1, 0, 1 });  // output: not valid
Validate(new[] { -1, 0, 0, 1 });  // output: valid

Para mais informações, consulte Padrão de Lista na especificação da linguagem C#.

Padrões de hierarquia fechada

A partir de C# 15, uma switch expressão cujo tipo governante é uma closed classe é exaustiva quando os seus braços tratam todos os descendentes diretos dessa classe. O compilador não requer um arm padrão porque o switch é exaustivo:

public closed record class PaymentMethod;
public record class Cash : PaymentMethod;
public record class Card(string Last4) : PaymentMethod;
public record class BankTransfer(string Iban) : PaymentMethod;
public static string Describe(PaymentMethod method) => method switch
{
    Cash => "cash",
    Card(var last4) => $"card ending {last4}",
    BankTransfer(var iban) => $"bank transfer to {iban}",
    // No warning: every direct descendant of 'PaymentMethod' is handled.
};

Um interruptor de hierarquia fechada é exaustivo apenas quando todos os descendentes diretos são acessíveis a partir da localização do comutador. Se um descendente direto for menos acessível do que o tipo base fechado e não for visível no local do switch, o compilador considera-o não tratado e avisa que o switch não é exaustivo.

Por exemplo, uma classe base fechada public pode ter um internal descendente direto. O código na mesma assembleia vê o conjunto completo de descendentes, mas o código noutra assembleia não:

// Assembly 1
public closed record class Shape;
public record class Circle(double Radius) : Shape;
internal record class Triangle(double Base, double Height) : Shape;
// Same assembly: 'Triangle' is visible, so the switch is exhaustive.
internal static double Area(Shape shape) => shape switch
{
    Circle(var r) => Math.PI * r * r,
    Triangle(var b, var h) => 0.5 * b * h,
};
// Assembly 2
public static string Name(Shape shape) => shape switch
{
    Circle => "circle",
    // Warning CS8509: the switch expression doesn't handle all possible values.
    // 'Triangle' is a direct descendant of 'Shape' but isn't visible here.
};

Para restaurar a exaustividade no conjunto 2, adicione um braço de descarte (_ => ...), adicione um braço de classe base (Shape no exemplo anterior), ou torne todos os descendentes diretos pelo menos tão acessíveis quanto o tipo base fechado.

Quando o tipo governante é anulável, null é um valor adicional que o comutador deve gerir. Uma mudança PaymentMethod? que omite um null braço não é exaustiva mesmo quando todos os descendentes diretos são combinados.

A derivação a partir de uma classe fechada não é transitiva: um descendente não fechado de uma classe fechada pode ser derivado de noutros assemblies. O compilador trata apenas os descendentes diretos como o conjunto exaustivo. Para fazer uma mudança sobre um descendente, também beneficia da verificação de exaustividade, declare o descendente closed (ou sealed).

Como os descendentes indiretos não expandem o conjunto exaustivo da base fechada, não é necessário adicionar um braço para cada subtipo transitivo para satisfazer a exaustividade. A subsunção entre braços ainda funciona da mesma forma que para qualquer hierarquia de classes: um braço que corresponde a um tipo base cobre todos os subtipos, e um braço posterior que corresponde a um desses subtipos é inalcançável. Considere um fechado Vehicle cujos descendentes diretos são Car e Truck, mais um descendente Sedan indireto declarado noutra assembleia:

// Assembly 1
public closed record class Vehicle;
public record class Car(int Doors) : Vehicle;
public record class Truck(double PayloadTons) : Vehicle;

// Assembly 2
public record class Sedan(int Doors) : Car(Doors);

Uma transição Vehicle é exaustiva depois de tratar Car e Truck, mesmo que Sedan exista. O braço Car cobre todos os Sedan valores:

public static string Category(Vehicle v) => v switch
{
    Car => "car",
    Truck => "truck",
    // No warning. The 'Car' arm covers 'Sedan' through ordinary subtype matching.
};

Para despachar especificamente Sedan , coloque o braço à frente do braço Car . O Car braço mantém-se acessível porque ainda corresponde a tudo Car o que não é um Sedan:

public static string CategorySedanFirst(Vehicle v) => v switch
{
    Sedan => "sedan",
    Car => "car",      // Reachable: 'Car' values that aren't 'Sedan'.
    Truck => "truck",
};

Inverter esses dois braços produz um erro de subsunção, tal como aconteceria em qualquer outra hierarquia de classes. O compilador deteta que o braço Car já cobre Sedan:

string Category(Vehicle v) => v switch
{
    Car => "car",
    Sedan => "sedan",  // Error CS8510: the pattern is unreachable. It has already been handled by 'Car => ...'.
    Truck => "truck",
};

Se quiseres que a exaustividade siga a hierarquia mais abaixo, declara-se Carclosed. O compilador então trata cada descendente direto de Car (como Sedan) como o conjunto exaustivo enraizado em Car. Um interruptor cujo tipo de governo é Car exaustivo quando faz qualquer um dos seguintes procedimentos:

  • Lida com cada descendente direto de Car com o seu próprio braço.
  • Inclui um Car braço, que cobre todos os Car valores (incluindo todos os subtipos).
  • Inclui um braço de descarte (_ => ...) ou um braço para um tipo base de Car, como Vehicle.

Um comutador cujo tipo governante é Vehicle tem escolhas: Código que trata Car e Truck continua exaustivo, porque o braço Car cobre todos os subtipos de Car. Marcar Carclosed simplesmente dá-te uma segunda opção para essa mudança. Podes manter o braço único Car , ou substituí-lo por um braço por descendente direto de Car (juntamente com o Truck braço) e continuar a ser exaustivo.

A marcação Carclosed também torna implicitamente abstract, o que significa que já não podes criar instâncias de Car diretamente. Isso pode não encaixar no teu design. Se precisares Car de continuar instanciado, deixa-o aberto e despacha nos subtipos específicos que te interessam, ordenando armas como mostrado anteriormente.

Parâmetro de tipo que governa os tipos

Uma switch expressão cujo tipo governante é um parâmetro de tipo restrito a uma classe fechada é exaustiva nos mesmos termos que um interruptor sobre a própria classe fechada. Para despachar numa hierarquia fechada a partir de código genérico, restringir o parâmetro de tipo à base fechada e tratar de todos os descendentes diretos:

public static string Describe<X>(X method) where X : PaymentMethod => method switch
{
    Cash => "cash",
    Card(var last4) => $"card ending {last4}",
    BankTransfer(var iban) => $"bank transfer to {iban}",
    // No warning: 'X' is constrained to the closed type 'PaymentMethod',
    // so handling every direct descendant exhausts the switch.
};

Para mais informações, consulte o modificador fechado. Para a especificação, veja Hierarquias fechadas.

Padrões sindicais

A partir de C# 15, quando o valor recebido de um padrão é do tipo union, os padrões desdobram automaticamente a união. Aplicam-se à propriedade do Value sindicato e não ao valor sindical em si. Este comportamento torna a união transparente à correspondência de padrões:

public record class Cat(string Name);
public record class Dog(string Name);
public union Pet(Cat, Dog);

string Describe(Pet pet) => pet switch
{
    Dog d => d.Name,
    Cat c => c.Name,
};

Dois padrões são exceções: o var padrão e o padrão de descarte _ aplicam-se ao valor da união em si, não à sua Value propriedade.

O null padrão verifica se o sindicato Value é nulo. Para uniões baseadas em classes, null também tem sucesso quando a própria referência sindical é nula.

Quando um tipo de união fornece o padrão de acesso não-boxing (HasValue e TryGetValue os membros), o compilador usa esses membros para evitar caixas de casos de valor durante a correspondência de padrões.

Para mais informações, veja correspondência da União. Para a especificação, veja Sindicatos.

Especificação da linguagem C#

Para mais informações, consulte a secção Padrões e correspondência de padrões da especificação da linguagem C#, incluindo:

Consulte também