Pattern Matching em C# - Aula Prática

Programação Aplicada em .NET • Prof. Me. William Tenório

Sobre esta aula

Na aula teórica vimos Pattern Matching: o recurso do C# que permite verificar se um valor ou objeto corresponde a um determinado padrão, considerando seu tipo, seu valor ou o estado de suas propriedades — em vez de apenas comparar valores com if tradicional.

Nesta aula prática vamos rodar códigos prontos, observar a saída, e principalmente alterar partes desses códigos para ver como cada padrão se comporta. Depois, você vai resolver alguns exercícios mais completos aplicando o que aprendeu.

Criando o Projeto no VS Code

Siga este roteiro para criar o projeto de console em .NET desta aula:

  1. Abra o Visual Studio Code.
  2. Crie uma pasta com o seu nome em um local do computador de que você vá se lembrar depois (ex.: Área de Trabalho ou Documentos).
  3. No VS Code, vá em File > Open Folder e selecione a pasta criada.
  4. Abra o terminal integrado em Terminal > New Terminal.
  5. No terminal, execute os comandos abaixo.
dotnet new console -n PatternMatching
cd PatternMatching
dotnet run

Todo código abaixo pode ser colado dentro do método Main (ou substituindo o conteúdo de Program.cs, no caso dos top-level statements). Depois de colar e alterar o código, salve o arquivo e rode dotnet run novamente para ver o novo resultado.

Atenção à ordem no Program.cs Se o seu projeto foi criado com o SDK mais recente, o Program.cs vem sem um Main visível (são os chamados top-level statements). Nesse formato, o C# aceita declarar classes no mesmo arquivo, mas todo código "solto" (as instruções dos exemplos) precisa vir antes de qualquer class. Se uma classe aparecer no meio do arquivo, antes de alguma instrução solta, o compilador acusa o erro top-level statements must precede namespace and type declarations. Por isso: cole a hierarquia de classes (próxima seção) sempre por último, depois de todo o código dos exemplos.

A hierarquia de classes usada nos exemplos

Todos os exemplos desta aula usam a mesma hierarquia de funcionários vista na teoria. Cole este trecho uma única vez, no final do seu Program.cs, depois de todo o código dos exemplos (veja o aviso acima) — os exemplos seguintes vão reaproveitá-lo.

public abstract class Funcionario
{
    public string Nome { get; set; }
    public int Idade { get; set; }
}

public class Desenvolvedor : Funcionario
{
    public string Linguagem { get; set; }
}

public class Gerente : Funcionario
{
    public int Equipe { get; set; }
}

public class Estagiario : Funcionario
{
    public string Curso { get; set; }
}

Exemplo 1 — Da forma tradicional ao is com captura de variável

Compare as duas formas abaixo de verificar o tipo de um objeto. A primeira é a forma tradicional, que precisa de um cast depois de descobrir o tipo; a segunda usa pattern matching para verificar o tipo e já criar a variável no mesmo passo.

Funcionario f1 = new Desenvolvedor { Nome = "Ana", Idade = 25, Linguagem = "C#" };

// Forma tradicional
if (f1 is Desenvolvedor)
{
    Desenvolvedor devCast = (Desenvolvedor)f1;
    Console.WriteLine($"[Tradicional] Dev: {devCast.Nome}");
}

// Com pattern matching (is + captura de variável)
if (f1 is Desenvolvedor dev)
{
    Console.WriteLine($"[Pattern Matching] Dev: {dev.Nome}, fala {dev.Linguagem}");
}

Desafios:

Pense e responda:

Exemplo 2 — Property Patterns

Além do tipo, um pattern pode verificar o estado das propriedades do objeto ao mesmo tempo, sem precisar de um if aninhado.

Funcionario f2 = new Desenvolvedor { Nome = "Bia", Idade = 17, Linguagem = "Python" };

if (f2 is Desenvolvedor { Idade: >= 18 } adulto)
{
    Console.WriteLine($"{adulto.Nome} é um dev maior de idade.");
}
else
{
    Console.WriteLine($"{f2.Nome} não é um dev maior de idade (ou não é dev).");
}

if (f2 is Desenvolvedor { Linguagem: "Python" })
{
    Console.WriteLine("Esse dev programa em Python.");
}

Desafios:

Exemplo 3 — Pattern Matching com switch (switch expression)

Quando temos vários tipos possíveis para tratar, o switch de pattern matching substitui uma longa cadeia de if / else if.

static string Descrever(Funcionario funcionario)
{
    return funcionario switch
    {
        Desenvolvedor d => $"Dev: {d.Nome} - {d.Linguagem}",
        Gerente g => $"Gerente: {g.Nome} - equipe de {g.Equipe}",
        _ => "Funcionário desconhecido"
    };
}

// Uso
Console.WriteLine(Descrever(new Desenvolvedor { Nome = "Ana", Linguagem = "C#" }));
Console.WriteLine(Descrever(new Gerente { Nome = "Carlos", Equipe = 5 }));

Desafios:

Exemplo 4 — Relational Patterns

O C# também permite padrões relacionais (>=, >, < etc.) dentro de um switch, o que é ótimo para classificar valores em faixas.

static string Classificar(double nota)
{
    return nota switch
    {
        >= 9 => "Excelente",
        >= 7 => "Aprovado",
        >= 5 => "Recuperação",
        _ => "Reprovado"
    };
}

double[] notas = { 9.5, 7.2, 5.5, 3.0 };

foreach (double nota in notas)
{
    Console.WriteLine($"Nota {nota}: {Classificar(nota)}");
}

Desafios:

Pense e responda:

Exemplo 5 — Combinando patterns com or

Patterns podem ser combinados com or (e também and) para expressar condições mais ricas em uma única verificação.

Funcionario f3 = new Desenvolvedor { Nome = "Diego", Idade = 16, Linguagem = "C#" };

if (f3 is Desenvolvedor { Idade: >= 18 } or Desenvolvedor { Linguagem: "C#" })
{
    Console.WriteLine("Atende à condição: é maior de idade OU programa em C#.");
}
else
{
    Console.WriteLine("Não atende à condição.");
}

Desafios:

Exercícios

Agora é sua vez de construir soluções mais completas usando os padrões vistos acima. Use a hierarquia de classes já criada (ou adapte-a conforme cada exercício pedir).

Exercício 1 — Relatório de funcionários

Crie uma List<Funcionario> contendo pelo menos 5 objetos, misturando Desenvolvedor, Gerente e Estagiario. Percorra a lista com foreach e, para cada funcionário, use um switch de pattern matching para exibir uma linha de relatório diferente conforme o tipo:

Repare que o caso do Gerente precisa de dois case diferentes, um para cada faixa de Equipe — combine tipo e property pattern no mesmo case.

Exercício 2 — Classificação de pedidos

Crie uma classe Pedido com as propriedades Valor (decimal) e ClienteVip (bool). Escreva um método string DefinirFrete(Pedido pedido) que usa um único switch com relational e property patterns para decidir o valor do frete, seguindo as regras abaixo (nessa ordem de prioridade):

Teste o método com pelo menos 4 pedidos diferentes, cobrindo cada uma das regras acima.

Exercício 3 — Motor de descontos (desafio)

Crie uma classe Cliente com Idade (int), AnosDeCasa (int) e Categoria (string, podendo ser "Bronze", "Prata" ou "Ouro"). Escreva um método decimal CalcularDesconto(Cliente cliente) que retorna um percentual de desconto combinando and/ or em property patterns, por exemplo:

Não existe uma única resposta certa aqui — o objetivo é você decidir como combinar as condições usando pattern matching e justificar a ordem escolhida para os case. Esteja preparado(a) para explicar sua solução.

Para refletir

Depois de resolver os exercícios, volte ao que vimos na teoria: Pattern Matching não é apenas uma forma diferente de escrever if. Pense em pelo menos uma situação, fora dos exemplos desta aula, em que decidir "o que fazer" depende do tipo ou da estrutura de um objeto (e não apenas de um valor simples) — esse é exatamente o tipo de cenário em que pattern matching se destaca. E lembre-se do alerta da teoria: nem todo if precisa virar pattern matching, apenas aqueles em que o tipo ou a estrutura do objeto realmente fazem parte da decisão.