Padrões correspondentes: expressões is e switch e operadores and, or e not em padrões

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

A linguagem C# faz referência a documentos da versão mais recentemente lançada da linguagem C#. Ele também contém a documentação inicial para funcionalidades em pré-visualizações públicas para o próximo lançamento do idioma.

A documentação identifica qualquer recurso introduzido pela primeira vez nas três últimas versões do idioma ou nas versões prévias públicas atuais.

Dica

Para descobrir quando um recurso foi introduzido pela primeira vez em C#, consulte o artigo sobre o histórico de versão da linguagem C#.

As seguintes expressões e instruções C# dão suporte à correspondência de padrões:

Nesses constructos, você pode corresponder 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: desconstruir um resultado de expressão e testar se os valores resultantes correspondem a padrões aninhados.
  • var padrão: corresponder a qualquer expressão e atribuir seu resultado a uma variável declarada.
  • Descartar padrão: para corresponder a qualquer expressão.
  • Padrões de lista: teste se uma sequência de elementos corresponde aos padrões aninhados correspondentes.

Logical, property, positional e list são padrões recursivos. Ou seja, eles podem conter padrões aninhados.

Para obter um exemplo de como usar esses padrões para criar um algoritmo controlado por dados, consulte Tutorial: Usar a correspondência de padrões para criar algoritmos controlados por tipo e controlados por dados.

Padrões de declaração e tipo

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

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 do resultado de uma expressão em tempo de execução deriva do tipo T, implementa a interface T ou existe outra conversão de referência implícita dele para T. Essa condição abrange relações de herança e implementações de interface. 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 ao método GetSourceLabel, o primeiro padrão corresponde a um valor de argumento porque o tipo int[] de tempo de execução do argumento deriva do tipo Array. Na segunda chamada para o método GetSourceLabel, o tipo List<T> de tempo de execução do argumento não deriva do tipo Array, mas implementa a interface ICollection<T>.
  • O tipo do resultado de uma expressão em tempo de execução é um tipo de valor anulável com o tipo subjacente T 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 o tipo T quando a expressão não é uma instância de ref struct.

Os padrões de declaração não consideram conversões definidas pelo usuário ou conversões de intervalo 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 uma expressão, use um descarte _ no lugar do nome de uma variável, como mostra o exemplo a seguir:

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 essa finalidade, use um padrão de tipo, como mostra o exemplo a seguir:

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 atende a qualquer uma das condições listadas acima.

Para verificar se há um padrão não nulo, use umpadrão de constantenull, como mostra o exemplo a seguir:

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

Para obter mais informações, consulte as seções padrão De declaração e padrão de tipo da especificação da linguagem C#.

Padrão de constante

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

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 conversível para o tipo constante, com uma exceção: uma expressão cujo tipo é Span<char> ou ReadOnlySpan<char> pode ser correspondido com cadeias de caracteres constantes.

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

if (input is null)
{
    return;
}

O compilador garante que ele não invoque um operador == de igualdade sobrecarregado pelo usuário ao avaliar a expressão x is null.

Use um padrão de constante negadonull para verificar se ele não é nulo, conforme mostrado no seguinte exemplo:

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

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

Padrões relacionais

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

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

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

Para verificar se um resultado de expressão está em um determinado intervalo, corresponda-o a um padrão conjuntivoand, 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 for convertido no tipo de uma constante usando uma conversão anulável ou de unboxing, um padrão relacional não corresponderá à expressão.

Para obter mais informações, consulte a seção Padrões relacionais da especificação da linguagem C#.

Padrões lógicos

Use os notcombinadores padrão e and , orpara criar os seguintes padrões lógicos:

  • Negaçãonot padrão que corresponde a uma expressão quando o padrão negated 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)
    {
        // ...
    }
    
  • Conjuntivoand padrão 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",
    };
    
  • Disjuntivoor padrão que corresponde a uma expressão quando 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, você pode usar os combinadores de padrão repetidamente em um padrão.

Precedência e ordem da avaliação

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

  • not
  • and
  • or

O padrão not é associado ao seu operando primeiro. O padrão and é associado após qualquer not associação de expressão padrão. O or padrão se associa a todos not e and os padrões se associam a operandos. O exemplo a seguir tenta corresponder a todos os caracteres que não são letras minúsculas. az Ele tem um erro, porque o padrão not é vinculado antes do and:

// 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 você deseja que o padrão se associe not à >= '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 fica mais importante à medida que os padrões se tornam mais complicados. Em geral, use parênteses para clarificar 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');

Observação

A ordem na qual o compilador verifica padrões que têm a mesma ordem de associação é indefinida. Em tempo de execução, o compilador pode verificar os padrões aninhados à direita de vários or padrões e vários and padrões primeiro.

Para obter mais informações, consulte a seção Combinadores de padrão da especificação da linguagem C#.

Padrão de propriedade

Use um padrão de propriedade para corresponder às propriedades ou campos de uma expressão com padrões aninhados, como mostra o exemplo a seguir:

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

Um padrão de propriedade 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.

Você pode adicionar uma verificação de tipo de tempo de execução e uma declaração de variável a um padrão de propriedade, como mostra o exemplo a seguir:

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

Esse constructo significa especificamente que o padrão de propriedade is { } corresponde a tudo que não é nulo e você pode usá-lo em vez de is not null 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 aninhado. Use um padrão de propriedade para corresponder partes de dados com 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 orcombinador de padrões e os tipos de registro.

Você pode referenciar propriedades aninhadas ou campos dentro de um padrão de propriedade. Essa capacidade é conhecida como um padrão de propriedade estendido. Por exemplo, você pode refatorar o método do exemplo anterior para o seguinte código equivalente:

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

Para obter mais informações, consulte a seção Padrão de propriedade do padrão C#.

Dica

Para melhorar a legibilidade do código, use a regra de estilo Simplificar padrão de propriedade (IDE0170 ). Ele sugere locais para usar padrões de propriedade estendidos.

Padrão posicional

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

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 Deconstruct , que o padrão usa para desconstruir um resultado de expressão.

Importante

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

Você também pode corresponder expressões de tipos de tupla com padrões posicionais. Usando essa abordagem, você pode corresponder várias entradas com vários padrões, como mostra o exemplo a seguir:

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 de tupla e parâmetros Deconstruct 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 de 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 fornecem implicitamente o método Deconstruct.

  • 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 a seguir:

    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 aninhado.

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

var padrão

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

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 padrão var é útil quando você precisa de uma variável temporária dentro de uma expressão booliana para manter o resultado de cálculos intermediários. Você também poderá usar um padrão var quando precisar realizar mais verificações em proteções de caso when de uma expressão ou instrução switch, 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 posicional(var x, var y).

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

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

Padrão de descarte

Use um padrão_ de descarte para corresponder a qualquer expressão, incluindo null, como mostra o exemplo a seguir:

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 manipule todos os valores de entrada possíveis. Se você não usar um padrão de descarte em uma expressão switch e nenhum dos padrões da expressão corresponder a uma entrada, o runtime gerará uma exceção. O compilador gera um aviso se uma expressão switch não manipular todos os valores de entrada possíveis.

Um padrão de descarte não pode ser um padrão em uma expressão is ou uma 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 não pode ser um padrão em uma switch expressão.

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

Padrão entre parênteses

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

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

Padrões de lista

Você pode corresponder uma matriz ou uma lista com uma sequência de padrões, como mostra o exemplo a seguir:

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 quando 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 você também quiser capturar o elemento, use o padrão var, como mostra o exemplo a seguir:

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 em relação a um padrão de lista. Para corresponder elementos somente no início ou no final - ou ambos - de uma sequência de entrada, use o padrão.. de fatia, como mostra o exemplo a seguir:

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 obter mais informações, consulte Padrão de lista na especificação da linguagem C#.

Padrões de hierarquia fechados

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

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 comutador de hierarquia fechado é exaustivo somente quando cada descendente direto é acessível do local do comutador. Se um descendente direto for menos acessível do que o tipo base fechado e não estiver visível no site de comutador, o compilador o tratará como sem tratamento e avisará que a opção não é exaustiva.

Por exemplo, uma classe base fechada public pode ter um internal descendente direto. O código no mesmo assembly vê o conjunto completo de descendentes, mas o código em outro assembly 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 o esgotamento no assembly 2, adicione um braço de descarte (_ => ...), adicione um braço de classe base (Shape no exemplo anterior) ou torne cada descendente direto pelo menos tão acessível quanto o tipo base fechado.

Quando o tipo de controle é anulável, null é um valor adicional que a opção deve manipular. Uma troca PaymentMethod? que omite um null braço não é exaustiva mesmo quando cada descendente direto é correspondido.

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

Como os descendentes indiretos não expandem o conjunto exaustivo da base fechada, você não precisa adicionar um braço para cada subtipo transitivo para satisfazer o esgotamento. A subsumpição entre braços ainda funciona da mesma maneira que funciona para qualquer hierarquia de classe: um braço que corresponde a um tipo base abrange cada subtipo e um braço posterior que corresponde a um desses subtipos é inacessível. Considere um fechado Vehicle cujos descendentes diretos são Car e Truck, além de um descendente Sedan indireto declarado em outro assembly:

// 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 troca Vehicle é exaustiva depois de manipular Car e Truck, mesmo que Sedan exista. O Car braço 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 expedir Sedan especificamente, coloque o braço antes do Car braço. O Car braço permanece acessível porque ainda corresponde a todos Car os que não são:Sedan

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

Reverter esses dois braços produz um erro de subsumpição, assim como faria em qualquer outra hierarquia de classe. O compilador detecta que o Car braço já abrange 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 você quiser esgotamento para seguir a hierarquia mais abaixo, declare-se Carclosed. Em seguida, o compilador trata todos os descendentes diretos de Car (como Sedan) como o conjunto exaustivo enraizado em Car. Um comutador cujo tipo de controle é Car exaustivo quando faz qualquer um dos seguintes procedimentos:

  • Manipula cada descendente direto com Car seu próprio braço.
  • Inclui um Car braço, que abrange 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 de controle é Vehicle tem opções: código que manipula Car e Truck ainda é exaustivo, pois o Car braço cobre todos os subtipos de Car. A marcação Carclosed simplesmente oferece uma segunda opção para essa opção. Você pode manter o braço único Car ou substituí-lo por um braço por descendente direto de Car (ao lado do Truck braço) e ainda ser exaustivo.

A marcação Carclosed também o torna implicitamente abstract, o que significa que você não pode mais criar instâncias diretamente Car . Isso pode não se ajustar ao seu design. Se você precisar Car permanecer instanciável, deixe-o aberto e desmarca os subtipos específicos que você se importa ordenando braços, conforme mostrado anteriormente.

Tipos de controle de parâmetro de tipo

Uma switch expressão cujo tipo de controle é um parâmetro de tipo restrito a uma classe fechada é exaustiva nos mesmos termos que uma alternância sobre a própria classe fechada. Para expedir em uma hierarquia fechada do código genérico, restrinja o parâmetro de tipo à base fechada e manipule 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 obter mais informações, consulte o modificador fechado. Para obter a especificação, consulte hierarquias fechadas.

Padrões de união

A partir do C# 15, quando o valor de entrada de um padrão é um tipo de união, os padrões desembrulham automaticamente a união. Eles se aplicam à propriedade do Value sindicato em vez do próprio valor sindical. Esse comportamento torna a união transparente para a 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 _ se aplicam ao próprio valor da união, não à sua Value propriedade.

O null padrão verifica se a união Value é nula. Para uniões baseadas em classes, null também é bem-sucedida 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 membros), o compilador usa esses membros para evitar casos de tipo de valor de boxe durante a correspondência de padrões.

Para obter mais informações, consulte Correspondência da União. Para obter a especificação, consulte Unions.

Especificação da linguagem C#

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

Veja também