Visão geral dos padrões correspondentes

Dica

Este artigo faz parte da seção Conceitos Básicos para desenvolvedores que já conhecem pelo menos uma linguagem de programação e estão aprendendo C#. Se você é novo em programação, comece com os tutoriais Comece agora. Desenvolvedores experientes em C# podem usar a referência de padrões para a sintaxe e as regras completas.

Vindo de outro idioma? Os padrões do C# desempenham um papel semelhante ao pattern matching do Java e aos casos de match do Python. O C# usa padrões em is expressões, switch instruçõ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 padrão é essa expressão. O C# avalia a expressão de entrada; o resultado é o valor avaliado. O padrão testa se esse valor tem um tipo específico, é igual a um valor específico ou tem uma forma específica. Quando um padrão corresponde, seu código pode usar informações aprendidas pelo teste, como um tipo mais específico ou um valor extraído de um objeto.

Você pode usar um padrão em três contextos:

  • No lado direito do operador is.
  • No rótulo case de uma instrução switch.
  • Em um ramo de uma expressão switch.

Os padrões geralmente são mais claros do que uma sequência de instruções de comparação, porque cada ramificação descreve os dados com os quais ela lida. Por exemplo, o método a seguir 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 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 das chaves é um braço de comutador. O padrão é exibido antes =>e o resultado aparece depois dele. O C# avalia a expressão de entrada e, em seguida, seleciona o primeiro ramo, na ordem em que aparece no texto, cujo padrão corresponde e cuja cláusula de guarda opcional é true. A guarda opcional when é uma condição booleana adicional que é escrita após o padrão. O exemplo anterior mostrou os seguintes padrões:

  • null é um padrão constante. Ele testa se a delivery expressão é avaliada como null.
  • ExpressDelivery express é um padrão de declaração com duas partes. ExpressDelivery é a parte de ensaio de tipo. Ele 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 de variável: ela declara uma variável nomeada express e atribui o objeto correspondente ExpressDelivery a ela.
  • StandardDelivery { Days: <= 2 } começa com um teste de tipo. StandardDelivery testa se o valor avaliado é um objeto não nulo desse tipo. As chaves contêm um padrão de propriedade. Days nomeia a propriedade a ser inspecionada, de modo que a Days expressão de propriedade se torna a entrada para o 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. Ele corresponde a todos os valores avaliados, incluindo null. Como os braços anteriores já tratam de null, entregas expressas e entregas padrão que chegam em até dois dias, este braço final trata de todos os valores restantes.

Um braço sem guarda when está desprotegido. Todos os braços no primeiro exemplo são desprotegidos. Se um braço anterior sem guarda corresponder a todos os valores avaliados aos quais um braço posterior poderia corresponder, o braço posterior estará subsumido. Um padrão subsumido é aquele em que todo valor de entrada possível que coincida com ele já tenha sido abrangido por um ramo anterior do switch. Isso nunca pode ser compatível, portanto o compilador gera um erro. O braço de descarte (_) deve ser o último porque corresponde a cada expressão de entrada. Um braço protegido não subsume um braço posterior com base apenas em seu padrão porque o guarda pode ser false.

Uma expressão de comutador é exaustiva quando seus braços manipulam todas as expressões de entrada possíveis. O primeiro exemplo é exaustivo porque seu braço de descarte final manipula qualquer coisa que os braços anteriores não correspondam. O compilador avisa quando detecta um valor de entrada potencial que nenhum braço manipula. O compilador não pode provar esgotamento para cada combinação de padrões, mas esse diagnóstico ajuda você a escrever o código correto de correspondência de padrões. Para ver as regras detalhadas de correspondência, subsunção e exaustividade, consulte a referência de padrões.

Testar uma condição com is

Use o is operador quando precisar de um teste booliano. O código a seguir avalia a delivery expressão e aplica o padrão ExpressDelivery expressde declaração. A parte de tipo corresponde quando o valor avaliado não é nulo e seu tipo de tempo de execução é compatível com ExpressDelivery. Quando o padrão corresponde, 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 controla se uma variável local foi atribuída antes de seu código lê-la. Esse rastreamento é chamado de atribuição definida. Dentro do bloco if, a condição só pode ser true quando o padrão atribui o objeto correspondente a express. O compilador, portanto, sabe que express é definitivamente atribuído lá. Seu código pode usar com segurança sua propriedade TrackingCode. Não é necessário um cast separado. Se você precisar apenas do teste de tipo e não precisar declarar uma variável, use um padrão de tipo, como delivery is ExpressDelivery.

Use is null ou is not null para verificações nulas. Esses padrões não chamam um operador != ou == definido pelo usuário.

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 obter mais diretrizes de segurança nula, consulte tipos de referência anuláveis.

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

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

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

A instrução switch a seguir informa uma atualização sobre a entrega. O branch de entrega expressa grava duas mensagens, portanto, uma instrução se 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 expressão case aplica um padrão à expressão delivery. A seção correspondente pode executar qualquer número de instruções antes de break sair do switch. A seção default lida com tudo o que os casos anteriores não correspondem.

A expressão de comutador a seguir 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 de comutador é concisa porque cada braço tem a mesma finalidade: produzir o valor retornado. Use uma instrução switch quando as ramificações executarem ações e use uma expressão switch quando as ramificações calcularem um único resultado.

Categorias de Padrões

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

Categoria de padrão O que ele testa
Declarações, constantes e var padrões Um tipo de tempo de execução, um valor constante específico ou qualquer valor que você deseja capturar
Padrões de tipo Um tipo de tempo de execução sem declarar uma variável
Padrões de propriedade e de posição Propriedades, campos ou valores desconstruídos
Padrões relacionais e lógicos Comparações e combinações, como and, ore not
Padrões de lista Os valores e a forma de uma lista ou matriz
Descartar padrões e itens descartados Qualquer valor restante ou um valor que seu código ignora intencionalmente

Os artigos básicos vinculados na tabela fornecem cobertura focada das categorias documentadas atualmente nesta seção. Para obter sintaxe completa e exemplos para todas as categorias de padrão, consulte a referência de padrões.

Consulte também