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:
- Abra o Visual Studio Code.
- 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).
- No VS Code, vá em File > Open Folder e selecione a pasta criada.
- Abra o terminal integrado em Terminal > New Terminal.
- 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:
- Troque
f1para umnew Gerente { Nome = "Carlos", Idade = 40, Equipe = 5 }e rode de novo. O que acontece com as duas mensagens? - Adicione um terceiro bloco
if (f1 is Gerente ger)exibindo o nome e o tamanho da equipe. - Tente usar
devCastoudevfora do blocoifem que foram criados. O que o compilador avisa?
Pense e responda:
- Quantas "perguntas" o código tradicional precisa fazer (verificar tipo + fazer cast) até conseguir usar
Linguagem? E na versão com pattern matching?
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:
- Mude a
Idadedef2para20e rode de novo. Qual mensagem aparece agora? - Combine as duas condições em um único pattern:
Desenvolvedor { Idade: >= 18, Linguagem: "Python" }. - Crie um
Gerentee escreva um pattern que verifica se a propriedadeEquipeé maior que3.
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:
- Crie a classe
Estagiario(já declarada na seção anterior) e adicione um novocaseao switch para tratá-la, exibindo oCurso. - Chame
Descreverpassando um objetoFuncionariocriado diretamente (nãoDesenvolvedornemGerente— dica: isso não é possível porque a classe é abstrata; troque por outra subclasse semcasecorrespondente) e veja o caso_sendo usado. - Remova o caso
_e tente compilar. O que o compilador diz sobre cobrir todos os casos?
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:
- Adicione mais notas ao array
notas, incluindo valores "de borda" como9.0,7.0e5.0. Confira se o resultado bate com o que você esperava. - Troque a ordem das faixas no switch (coloque
>= 5antes de>= 7) e rode de novo. O que muda? Por que isso acontece? - Reescreva a mesma lógica usando
if / else iftradicional e compare a legibilidade das duas versões.
Pense e responda:
- Por que a ordem das faixas em um switch com relational patterns é importante?
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:
- Mude a
Idadedef3para30e depois aLinguagempara"Java", testando as combinações até entender quando a condição passa a ser falsa. - Troque o
orporande observe como a condição fica mais restritiva.
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:
- Para
Desenvolvedor:"[DEV] Nome - Linguagem". - Para
GerentecomEquipe >= 5:"[GERENTE SÊNIOR] Nome". - Para
Gerentecom equipe menor:"[GERENTE] Nome". - Para
Estagiario:"[ESTAGIÁRIO] Nome - Curso".
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):
- Cliente VIP com pedido
>= 100→"Frete grátis (VIP)". - Pedido
>= 200(não VIP) →"Frete grátis". - Pedido
>= 50→"Frete R$ 10". - Qualquer outro caso →
"Frete R$ 25".
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:
- Categoria
"Ouro"e mais de 2 anos de casa → 20%. - Categoria
"Prata"e mais de 2 anos de casa, ou categoria"Ouro"com até 2 anos → 10%. - Idade
>= 60(independente da categoria) → pelo menos 15% (decida como combinar essa regra com as demais). - Nenhuma das condições acima → 0%.
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.