Visão geral da correspondência de padrões

Tip

Este artigo faz parte da secção Fundamentos para programadores que já conhecem pelo menos uma linguagem de programação e estão a aprender C#. Se és novo na programação, começa primeiro pelos tutoriais para começar . Desenvolvedores experientes de C# podem usar a referência de padrões para a sintaxe e regras completas.

Vem de outra língua? Os padrões de C# têm um papel semelhante ao da correspondência de padrões do Java e ao dos casos match do Python. C# utiliza padrões em is expressões, switch afirmações e switch expressões.

A correspondência de padrões aplica um padrão a uma expressão. Um padrão é uma condição para testar a expressão de entrada. A entrada do padrão é essa expressão. C# avalia a expressão de entrada; o resultado é o valor avaliado. O padrão testa se esse valor tem um tipo particular, se é igual a um valor particular ou se tem uma forma particular. Quando um padrão coincide, o seu código pode usar a informação aprendida pelo teste, como um tipo mais específico ou um valor extraído de um objeto.

Pode usar um padrão em três contextos:

  • À direita do operador is.
  • Num rótulo de uma switch instrução case.
  • Num ramo de uma expressão switch.

Os padrões são frequentemente mais claros do que uma sequência de instruções de comparação porque cada ramo descreve os dados que processa. Por exemplo, o método seguinte usa uma switch expressão para escolher uma mensagem de entrega:

static string GetDeliveryMessage(Delivery? delivery) =>
    delivery switch
    {
        null => "No delivery was scheduled.",
        ExpressDelivery express => $"Express package {express.TrackingCode}",
        StandardDelivery { Days: <= 2 } => "Standard delivery arriving soon",
        _ => "Standard delivery"
    };

Os padrões recursivos têm as suas próprias expressões de entrada. Em StandardDelivery { Days: <= 2 }, o padrão externo recebe a expressão delivery. O padrão recursivo <= 2 recebe a expressão de propriedade Days do objeto correspondente StandardDelivery.

A expressão anterior switch é a expressão de entrada. Cada linha dentro dos suportes é um braço de comutação. O padrão aparece antes de =>, e o resultado aparece depois deste. C# avalia a expressão de entrada e, em seguida, seleciona o primeiro ramo, pela ordem em que aparece no texto, cujo padrão coincida e cuja guarda opcional when seja true. A guarda opcional when é uma condição booleana adicional escrita após o padrão. O exemplo anterior mostrou os seguintes padrões:

  • null é um padrão constante. Testa se a expressão delivery resulta em null.
  • ExpressDelivery express é um padrão de declaração com duas partes. ExpressDelivery é a parte do ensaio de tipo. Testa se o valor avaliado é um objeto não nulo cujo tipo de tempo de execução é compatível com ExpressDelivery. express é a designação da variável: declara uma variável nomeada express e atribui-lhe o objeto correspondente ExpressDelivery .
  • StandardDelivery { Days: <= 2 } Começa com um teste de tipo. StandardDelivery testa se o valor avaliado é um objeto não nulo desse tipo. As chavetas contêm um padrão de propriedades. Days nomeia a propriedade a inspecionar, de modo que a Days expressão da propriedade se torna a entrada do padrão recursivo. A <= 2 parte é um padrão relacional, que testa se o valor avaliado é menor ou igual a 2.
  • _ (sublinhado) é o padrão de descarte. Corresponde a todos os valores avaliados, incluindo null. Como os braços anteriores já processam null entregas expresso e entregas padrão entregues no prazo de dois dias, este braço final processa todos os valores restantes.

Um braço sem guarda when está desprotegido. Todas as armas no primeiro exemplo estão desprotegidas. Se um braço anterior sem proteção corresponder a todos os valores avaliados que um braço posterior poderia corresponder, o braço posterior é subsumido. Um padrão subsumido é aquele em que qualquer valor de entrada possível que lhe corresponda já tenha sido abrangido por um ramo anterior do switch. Nunca pode corresponder, por isso o compilador reporta um erro. O braço de descarte (_) tem de vir por último porque corresponde a todas as expressões de entrada. Um braço guardado não subsume um braço posterior apenas com base no seu padrão, porque a guarda pode ser false.

Uma expressão switch é exaustiva quando os seus ramos abrangem todas as expressões de entrada possíveis. O primeiro exemplo é exaustivo porque o ramo final de descarte trata de tudo o que os ramos anteriores não abrangem. O compilador avisa quando deteta um valor de entrada potencial que nenhum braço controla. O compilador não pode provar a exaustividade para todas as combinações de padrões, mas este diagnóstico ajuda-te a escrever código correto de correspondência de padrões. Para regras detalhadas de correspondência, subsunção e exaustividade, consulte a referência dos padrões.

Teste uma condição com is

Use o is operador quando precisar de um teste booleano. O código seguinte avalia a expressão delivery e aplica o padrão de declaração ExpressDelivery express. A porção do tipo corresponde quando o valor avaliado não é nulo e o seu tipo em tempo de execução é compatível com ExpressDelivery. Quando o padrão coincide, a sua designação de variável declara express:

static void PrintTrackingCode(Delivery delivery)
{
    if (delivery is ExpressDelivery express)
    {
        Console.WriteLine($"Track express package {express.TrackingCode}");
    }
}

O compilador verifica se foi atribuído um valor a uma variável local antes de o seu código a ler. Este acompanhamento chama-se atribuição definida. Dentro do if bloco, a condição só pode ser true quando o padrão atribuiu o objeto correspondente a express. O compilador, portanto, sabe que express está definitivamente atribuído nesse ponto. O seu código pode usar com segurança a sua propriedade TrackingCode. Não precisas de um elenco separado. Se só precisares do teste de tipo e não precisares de declarar uma variável, usa um padrão de tipo, como delivery is ExpressDelivery.

Usar is null ou is not null para verificações nulas. Estes padrões não invocam um operador == ou != definido pelo utilizador.

static void PrintPackageStatus(Delivery? delivery)
{
    if (delivery is null)
    {
        Console.WriteLine("No package is available.");
    }
    else
    {
        Console.WriteLine("A package is ready to track.");
    }
}

Para mais orientações sobre segurança nula, veja tipos de referência anuláveis.

Escolha entre uma afirmação e uma expressão

Os padrões funcionam com ambas as formas de switch:

  • Use uma switch instrução quando cada partida deve executar uma ou mais instruções.
  • Use uma switch expressão em que cada correspondência deve produzir um valor.

A seguinte declaração de troca reporta uma atualização de entrega. O ramo express-delivery escreve duas mensagens, pelo que uma instrução encaixa naturalmente:

static void PrintDeliveryUpdate(Delivery? delivery)
{
    switch (delivery)
    {
        case null:
            Console.WriteLine("No delivery was scheduled.");
            break;
        case ExpressDelivery express:
            Console.WriteLine($"Express delivery {express.TrackingCode} is ready.");
            Console.WriteLine("Notify the priority desk.");
            break;
        case StandardDelivery standard:
            Console.WriteLine($"Standard delivery arrives in {standard.Days} days.");
            break;
        default:
            Console.WriteLine("Another delivery type is scheduled.");
            break;
    }
}

Cada case aplica um padrão à expressão delivery. A secção correspondente pode executar qualquer número de instruções antes de break sair do switch. A secção default abrange tudo o que não corresponda a nenhum dos casos anteriores.

A seguinte expressão de troca substitui uma if / else if cadeia que atribui um resultado:

static string GetStatusMessage(StandardDelivery delivery) =>
    delivery.Days switch
    {
        0 => "Delivered today",
        1 => "Arriving tomorrow",
        <= 3 => "Arriving soon",
        _ => "In transit"
    };

Uma expressão switch é concisa porque cada braço tem o mesmo propósito: produzir o valor devolvido. Use uma instrução switch quando os ramos executem ações, e use uma expressão switch quando os ramos calculam um resultado.

Categorias de padrões

C# inclui padrões para tipos comuns de testes de dados:

Categoria de padrão O que testa
Declarações, constantes e var padrões Um tipo de tempo de execução, um valor constante específico, ou qualquer valor que pretenda capturar
Padrões tipográficos Um tipo de tempo de execução sem declarar uma variável
Padrões de propriedade e posicionamento Propriedades, campos ou valores desconstruídos
Padrões relacionais e lógicos Comparações e combinações como and, or, e not
Padrões de listas Os valores e a forma de uma lista ou array
Padrões de descarte e descartes Qualquer valor restante, ou um valor que o teu código ignora intencionalmente

Os artigos de Fundamentos ligados na tabela fornecem uma cobertura focada das categorias atualmente documentadas nesta secção. Para sintaxe completa e exemplos para todas as categorias de padrões, consulte a referência de padrões.

Consulte também