C# Expressões switch
Muitas vezes, um switch só existe para dar um valor a uma variável: o nome do dia, o conceito da nota, o desconto da forma de pagamento. Escrito com case e break, isso ocupa três linhas por opção. A expressão switch C# 8 faz o mesmo trabalho numa tabela compacta: cada linha diz "se for isto, o valor é aquilo". É o operador ternário para muitas opções.
- Como escrever
valor switch { padrão => resultado, … }. - O descarte
_("qualquer outra coisa") e o que acontece sem ele (avisoCS8509e uma exceção). - Padrões simples: valores,
<,>=,andeor. - Por que, aqui, o compilador avisa quando a ordem está errada (
CS8510). - Os erros mais comuns:
defaultno lugar de_,:no lugar de=>, tipos diferentes nos braços.
Todos os programas desta aula rodaram com o .NET 10 (C# 14) na máquina fictícia da Ana, na pasta ~/cs, com dotnet run arquivo.cs.
A ideia: a tabela de preços na parede
Na lanchonete, a tabela na parede diz: "Pastel → R$ 8; Coxinha → R$ 6; Suco → R$ 7; Outros → pergunte no caixa". Ninguém lê isso como uma lista de ordens ("se for pastel, faça isto, depois saia"). É uma tabela de consulta: você procura o item e lê o valor ao lado.
A expressão switch é essa tabela. Do lado esquerdo da seta =>, o que procurar; do lado direito, o valor. E a última linha, "outros", é o _.
1. A primeira expressão switch
int dia = 3;
string nome = dia switch
{
1 => "domingo",
2 => "segunda-feira",
3 => "terça-feira",
4 => "quarta-feira",
5 => "quinta-feira",
6 => "sexta-feira",
7 => "sábado",
_ => "dia inválido"
};
Console.WriteLine($"Hoje é {nome}.");
$ dotnet run dia.cs Hoje é terça-feira.
- dia switch
- O valor que vai ser consultado vem antes da palavra
switch, e sem parênteses. (Na instruçãoswitch, era o contrário:switch (dia).) - { … }
- As chaves envolvem os braços (em inglês, arms): as linhas da tabela.
- 3 => "terça-feira"
- Um braço: à esquerda da seta
=>, o padrão (o que procurar); à direita, o valor que a expressão inteira vale quando o padrão combina. - ,
- Os braços são separados por vírgula. A vírgula depois do último braço é opcional.
- _ => "dia inválido"
- O descarte
_combina com qualquer valor. É o "outros" da tabela, o equivalente dodefault. Fica sempre por último. - };
- Depois da chave, o ponto e vírgula: a linha toda é uma declaração
string nome = …;, e oswitché só o valor do lado direito.
Não existe case, nem break, nem a chance de "cair" no braço seguinte: o primeiro braço que combina dá o valor, e pronto.
A mesma coisa com a instrução switch
Para comparar, este é o começo do mesmo programa escrito com a instrução switch da aula anterior:
string nome;
switch (dia)
{
case 1:
nome = "domingo";
break;
case 2:
nome = "segunda-feira";
break;
// … mais 5 casos iguais …
default:
nome = "dia inválido";
break;
}
Vinte e oito linhas contra onze, e com nome = repetido em todo caso. Quando o objetivo é um valor, a expressão switch é a ferramenta certa. Quando cada caso faz coisas diferentes (várias linhas, várias ações), continue com a instrução switch.
Missão: a tabela da Ana parou na quinta-feira, e o dia 6 está caindo no "dia inválido". Acrescente os braços do 6 ("sexta-feira") e do 7 ("sábado"), antes do _.
Hoje é sexta-feira.
int dia = 6;
string nome = dia switch
{
1 => "domingo",
2 => "segunda-feira",
3 => "terça-feira",
4 => "quarta-feira",
5 => "quinta-feira",
_ => "dia inválido"
};
Console.WriteLine($"Hoje é {nome}.");
int dia = 6;
string nome = dia switch
{
1 => "domingo",
2 => "segunda-feira",
3 => "terça-feira",
4 => "quarta-feira",
5 => "quinta-feira",
6 => "sexta-feira",
7 => "sábado",
_ => "dia inválido"
};
Console.WriteLine($"Hoje é {nome}.");
2. Comparações: <, >= e companhia C# 9
O lado esquerdo do braço não precisa ser um valor exato. Ele pode ser uma comparação, sem repetir o nome da variável: >= 9 quer dizer "se a nota for maior ou igual a 9". Assim, a cadeia de else if do conceito vira:
double nota = 7.5;
string conceito = nota switch
{
>= 9 => "A",
>= 7 => "B",
>= 5 => "C",
_ => "D"
};
Console.WriteLine($"Nota {nota}: conceito {conceito}");
$ dotnet run conceito.cs Nota 7,5: conceito B
Os braços são testados de cima para baixo, como na cadeia de else if: 7,5 não é >= 9, mas é >= 7, então o valor é "B". Valem <, <=, > e >=, sempre comparando com uma constante (um número escrito no código).
Repare na seta de cada braço: >= 9 => "A" tem um >= ("maior ou igual") e depois um => ("vale"). São coisas diferentes, só parecidas.
Aqui, o compilador pega a ordem errada
Na aula de else if, a ordem errada (do menor para o maior) dava conceito C para a nota 9,5, sem nenhum aviso. Na expressão switch, o compilador percebe:
double nota = 7.5;
string conceito = nota switch
{
>= 5 => "C",
>= 7 => "B",
>= 9 => "A",
_ => "D"
};
Console.WriteLine(conceito);
$ dotnet run ordem.cs /home/ana/cs/ordem.cs(5,5): error CS8510: O padrão não está acessível. Ele já foi manipulado por um ARM anterior da expressão do comutador ou não é possível fazer a correspondência. /home/ana/cs/ordem.cs(6,5): error CS8510: O padrão não está acessível. Ele já foi manipulado por um ARM anterior da expressão do comutador ou não é possível fazer a correspondência. Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
A tradução é estranha ("ARM" é o arm, braço, que ficou em inglês e maiúsculo; "comutador" é o switch), mas a ideia é clara: "este padrão é inalcançável: um braço anterior já pegou todos os valores dele". Toda nota >= 7 (linha 5) também é >= 5, então o braço da linha 4 sempre ganha. Em inglês: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. Conserto: do maior para o menor, como na seção anterior.
Missão: o programa não compila (rode e veja o CS8510). Coloque os braços na ordem certa para que uma compra de R$ 250 ganhe 10%.
Você ganhou 10% de desconto
decimal compra = 250m;
int desconto = compra switch
{
>= 100m => 5,
>= 200m => 10,
>= 500m => 15,
_ => 0
};
Console.WriteLine($"Você ganhou {desconto}% de desconto");
decimal compra = 250m;
int desconto = compra switch
{
>= 500m => 15,
>= 200m => 10,
>= 100m => 5,
_ => 0
};
Console.WriteLine($"Você ganhou {desconto}% de desconto");
3. Juntar padrões: and e or C# 9
Dentro de um padrão, or quer dizer "ou" e and quer dizer "e". São palavras, não os || e && das condições comuns. A Ana quer separar as notas que não fazem sentido (abaixo de 0 ou acima de 10):
int nota = 11;
string situacao = nota switch
{
< 0 or > 10 => "nota inválida",
>= 6 => "aprovada",
_ => "reprovada"
};
Console.WriteLine($"Nota {nota}: {situacao}");
$ dotnet run invalida.cs Nota 11: nota inválida « 11 > 10: o primeiro braço pegou
Com and, dá para escrever uma faixa completa: >= 2 and <= 6 => "dia útil". E o or funciona com valores exatos e textos também. A lojinha da Ana dá 5% de desconto no Pix e no dinheiro:
string forma = "dinheiro";
decimal total = 200m;
decimal fator = forma switch
{
"pix" or "dinheiro" => 0.95m,
"cartão" => 1.00m,
_ => 1.00m
};
Console.WriteLine($"A pagar: R$ {total * fator:N2}");
$ dotnet run desconto.cs A pagar: R$ 190,00
| Padrão | Combina quando o valor… |
|---|---|
3, "pix", 's', true | é exatamente esse |
< 0, >= 9 | passa na comparação |
1 or 7 | é um dos dois |
>= 2 and <= 6 | passa nas duas comparações (está na faixa) |
not 0 | não é esse |
_ | qualquer coisa |
Esses são os padrões do dia a dia. Existem muitos outros (por tipo, por propriedade, por posição), e a aula Pattern Matching mostra todos.
4. Esqueceu o _? Aviso e exceção
Uma expressão switch tem de dar um valor: a variável do lado esquerdo do = precisa receber alguma coisa. Se nenhum braço combinar, não há valor para dar. O compilador avisa antes:
int dia = 9;
string nome = dia switch
{
1 => "domingo",
7 => "sábado"
};
Console.WriteLine(nome);
$ dotnet run semdescarte.cs /home/ana/cs/semdescarte.cs(2,19): warning CS8509: A expressão switch não manipula todos os valores possíveis do tipo de entrada (ela não é exaustiva). Por exemplo, o padrão '0' não é coberto. Unhandled exception. System.Runtime.CompilerServices.SwitchExpressionException: Non-exhaustive switch expression failed to match its input. Unmatched value was 9. at <PrivateImplementationDetails>.ThrowSwitchExpressionException(Object unmatchedValue) at Program.<Main>$(String[] args) in /home/ana/cs/semdescarte.cs:line 2 $ echo $? 134
- O aviso
CS8509, na compilação: "a expressão switch não trata todos os valores possíveis (não é exaustiva). Por exemplo, o 0 não está coberto". Como é só um aviso, o programa roda. - A exceção, ao rodar: com o dia 9, nenhum braço combinou, e o programa parou com
SwitchExpressionException("a expressão switch não exaustiva não encontrou o valor de entrada. O valor sem par foi 9"). Oline 2aponta a linha doswitch. As exceções saem em inglês no Linux. - O código de saída 134: o jeito do Linux dizer que o programa foi abortado. Um programa que termina bem sai com 0.
Conserto: um último braço _ => "dia inválido". Trate o aviso CS8509 como erro: ele quase sempre quer dizer que o programa vai quebrar com algum valor.
5. Os braços precisam combinar
Como no ternário, a expressão inteira tem um tipo só. Todos os braços precisam dar valores do mesmo tipo (ou de tipos que se convertem):
int estoque = 0;
var texto = estoque switch
{
0 => "esgotado",
_ => estoque
};
Console.WriteLine(texto);
$ dotnet run tipos.cs /home/ana/cs/tipos.cs(2,21): error CS8506: Não foi encontrado um tipo melhor para a expressão switch. Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Um braço dá string, o outro dá int, e o compilador "não encontrou um tipo" que sirva para os dois. Conserto: _ => estoque.ToString(), ou _ => $"{estoque} unidades".
6. Expressão switch é para valores, não para ações
Igual ao ternário: a expressão switch produz um valor, e não pode ficar sozinha numa linha.
int opcao = 1;
opcao switch
{
1 => Console.WriteLine("Ver saldo"),
_ => Console.WriteLine("Opção inválida")
};
$ dotnet run sozinho.cs /home/ana/cs/sozinho.cs(2,1): error CS0201: Somente as expressões de atribuição, chamada, incremento, diminuição, espera e novo objeto podem ser utilizadas como uma instrução Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Para fazer coisas em cada caso, use a instrução switch. Ou escolha o texto com a expressão e faça uma ação com ele:
Console.WriteLine(opcao switch
{
1 => "Ver saldo",
_ => "Opção inválida"
});
Ela também funciona direto numa interpolação (aqui com uma interpolação dentro da outra, no último braço):
int itens = 3;
Console.WriteLine($"Carrinho: {itens switch { 0 => "vazio", 1 => "1 item", _ => $"{itens} itens" }}");
$ dotnet run interp.cs Carrinho: 3 itens
Funciona, mas numa linha tão comprida fica difícil de ler. Guardar o resultado numa variável antes, como nos outros exemplos, quase sempre é mais claro.
Qual usar?
| Situação | Use |
|---|---|
| Escolher entre dois valores | Ternário: n == 1 ? "item" : "itens" |
| Escolher um valor entre três ou mais (valores exatos ou faixas) | Expressão switch (esta aula) |
| Fazer coisas diferentes em cada caso | Instrução switch |
Condições que não são sobre um mesmo valor (temChuva && idade > 60) | if / else if |
Quando dá errado
warning CS8509: A expressão switch não manipula todos os valores possíveis… e SwitchExpressionException
O que significa: falta o braço "qualquer outra coisa". Se aparecer um valor que nenhum braço cobre, o programa para com a exceção System.Runtime.CompilerServices.SwitchExpressionException e o código de saída 134.
Como consertar: acrescente _ => … como último braço.
error CS8510: O padrão não está acessível…
O que significa: um braço nunca vai ser usado, porque um braço anterior já pega todos os valores dele. Quase sempre: faixas fora de ordem, ou um _ que não ficou no fim.
Como consertar: com >=, do maior para o menor; o _ sempre por último.
error CS8505: Um literal padrão 'default' não é válido como um padrão…
$ dotnet run default.cs /home/ana/cs/default.cs(6,5): error CS8505: Um literal padrão 'default' não é válido como um padrão. Use outro literal (por exemplo, '0' ou 'null') conforme o necessário. Para corresponder a tudo, use um padrão de descarte '_'. /home/ana/cs/default.cs(6,5): error CS9060: Não é possível usar uma constante numérica ou padrão relacional em "int" porque herda ou estende "INumberBase<T>". Considere usar um padrão de tipo para restringir a um tipo numérico específico. Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: você escreveu default => "outro dia", como na instrução switch. Na expressão, não existe default. A própria mensagem dá a resposta: "para combinar com tudo, use o descarte _". O segundo erro, CS9060, é o compilador confuso com o mesmo default: ignore-o.
Como consertar: _ => "outro dia".
error CS1003: Erro de sintaxe, "=>" esperado
O que significa: você usou 1: "domingo" (os dois-pontos do case) no lugar da seta. Aparece um erro por braço.
Como consertar: 1 => "domingo".
error CS1003: Erro de sintaxe, "," esperado depois da chave
$ cat semponto.cs
int dia = 1;
string nome = dia switch
{
1 => "domingo",
_ => "outro dia"
}
Console.WriteLine(nome);
$ dotnet run semponto.cs
/home/ana/cs/semponto.cs(6,2): error CS1003: Erro de sintaxe, "," esperado
Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: faltou o ; depois da chave que fecha o switch (linha 6, coluna 2, logo depois da }). A mensagem fala em vírgula porque o compilador acha que a declaração continua (como em int a = 1, b = 2;); o que falta mesmo é o ponto e vírgula.
Como consertar: };.
error CS8506: Não foi encontrado um tipo melhor para a expressão switch.
O que significa: os braços dão valores de tipos que não combinam (texto num, número no outro).
Como consertar: deixe todos do mesmo tipo, com ToString() ou uma interpolação.
Referência rápida
| Forma | O que faz |
|---|---|
var r = x switch { 1 => "a", 2 => "b", _ => "c" }; | Valor de r conforme x. Braços testados de cima para baixo; o primeiro que combina ganha. |
>= 9 => "A" | Braço com comparação C# 9. |
1 or 7 => … | Um ou outro valor. |
>= 2 and <= 6 => … | Faixa. |
_ => … | Qualquer outro valor. Sempre o último. |
Sem _ | Aviso CS8509; com um valor não coberto, SwitchExpressionException. |
| Braço depois de outro que já cobre tudo | Erro CS8510. |
case, break, default | Não existem na expressão switch (são da instrução). |
Exercícios
1. A seta
Complete com o símbolo que separa o padrão do valor:
2. Qualquer outro
Complete com o padrão que combina com qualquer valor:
3. O que aparece?
Com int t = 28;, qual é o valor de t switch { < 15 => "frio", < 25 => "agradável", _ => "calor" }?
4. O que acontece ao rodar uma expressão switch sem _ quando nenhum braço combina?
5. Por que x switch { > 0 => "positivo", > 10 => "grande", _ => "outro" } não compila?
6. Qual é a forma certa de dizer "1 ou 7" num padrão?
Missão final: o ingresso do cinema
Missão: o cinema da Ana cobra R$ 15 de crianças com menos de 12 anos, R$ 30 de quem tem de 12 a 59 e R$ 15 de quem tem 60 ou mais. Troque o 30m fixo por uma expressão switch com comparações (< 12, < 60 e o _). Sem avisos. Com 8 anos, a saída deve ser:
Ingresso: R$ 15,00
int idade = 8;
decimal preco = 30m; // troque por uma expressão switch
Console.WriteLine($"Ingresso: R$ {preco:N2}");
int idade = 8;
decimal preco = idade switch
{
< 12 => 15m,
< 60 => 30m,
_ => 15m
};
Console.WriteLine($"Ingresso: R$ {preco:N2}");
Teste com 12, 59 e 60, os limites. Desafio: faça a idade negativa dar 0m e escreva a mensagem "idade inválida" (dica: um braço < 0 no começo).
Resumo
valor switch { padrão => resultado, …, _ => padrão }C# 8 escolhe um valor numa tabela. Semcase, sembreak, braços separados por vírgula e;no fim.- Os braços são testados em ordem; o primeiro que combina dá o valor.
- Padrões simples: valores,
</>=C# 9,and,or,note o descarte_. - Sem
_: avisoCS8509e, com um valor não coberto,SwitchExpressionException(saída 134). Braço impossível de alcançar: erroCS8510. - Todos os braços com o mesmo tipo (
CS8506). Para ações, use a instruçãoswitch(CS0201).