Uma expressão switch mapeia um valor de entrada para um resultado. Ela foi adicionada no C# 8 e substitui o padrão comum de uma instrução switch em que cada case só retorna ou atribui alguma coisa:
// C# 8 and later
string StatusText(int code) => code switch
{
200 => "OK",
301 or 302 => "Redirect", // 'or' needs C# 9
404 => "Not Found",
>= 500 => "Server Error", // relational patterns need C# 9
_ => "Unknown"
};
Leia de cima para baixo: o valor antes de switch é testado contra cada braço em ordem, e o primeiro braço cujo padrão corresponde fornece o resultado. Não há case, nem break, nem a palavra-chave default; _ (o padrão de descarte) corresponde a qualquer coisa e serve de alternativa final.
No C# 7, o mesmo mapeamento é uma instrução switch em um método pequeno, com return em cada case:
Saída:
200 OK
302 Redirect
404 Not Found
503 Server Error
418 Unknown
A forma de expressão é mais curta principalmente porque cada braço ocupa uma linha e o corpo do método desaparece. Ela também é uma expressão, então pode ficar em qualquer lugar onde cabe um valor: um argumento, um trecho de interpolação, um return, um inicializador de campo.
Regras de sintaxe
var result = input switch
{
pattern1 => value1,
pattern2 when condition => value2,
_ => fallback
};
- A entrada vem primeiro, depois
switch. É o inverso da forma de instrução. - Cada braço é
pattern => expression. O lado direito precisa ser uma única expressão; para mais lógica, chame um método. - Os braços são separados por vírgulas. Uma vírgula depois do último braço é permitida.
- O conjunto todo é uma expressão, então a instrução que o usa termina com
;depois da chave de fechamento. - Todos os braços precisam produzir valores de um tipo comum.
var x = n switch { 0 => "zero", _ => 0 };não compila (CS8506, nenhum tipo melhor). Desde o C# 9, um tipo de destino declarado resolve:object x = n switch { 0 => "zero", _ => 0 };compila. - Todo braço precisa produzir um valor, então um braço não pode chamar um método
voidcomoConsole.WriteLine, e uma expressão switch não pode ficar sozinha como instrução (CS0201). Para ações, use a instrução switch. - Um braço pode lançar uma exceção em vez de produzir um valor:
_ => throw new ArgumentOutOfRangeException(nameof(input)). - O compilador rejeita um braço que nunca pode ser alcançado porque um braço anterior já o cobre, com o erro CS8510,
The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match.Coloque os braços específicos antes dos gerais.
Padrões relacionais e lógicos (C# 9)
O C# 9 adicionou <, <=, >, >= como padrões, e os combinadores and, or e not. Com eles, tabelas de intervalos ficam parecidas com uma especificação:
// C# 9 and later
string Grade(int score) => score switch
{
< 0 or > 100 => throw new ArgumentOutOfRangeException(nameof(score)),
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F"
};
string AgeGroup(int age) => age switch
{
< 13 => "child",
>= 13 and <= 19 => "teen",
_ => "adult"
};
bool IsLetter(char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';
Como os braços são testados em ordem, >= 80 só vê notas abaixo de 90. Padrões relacionais só aceitam constantes à direita; para comparar com uma variável, use uma guarda when. O equivalente em C# 7 de uma tabela de intervalos é uma cadeia de if:
Saída:
95 -> A
83 -> B
71 -> C
64 -> D
12 -> F
not é usado com mais frequência como not null: x is not null, ou um braço not null => x.Name.
Padrões de propriedade
Um padrão de propriedade corresponde aos membros de um objeto: { Property: pattern, ... }. Combinado com uma expressão switch, ele expressa regras de negócio sem uma cadeia de condições &&:
// C# 8 and later
decimal ShippingCost(Order order) => order switch
{
{ Total: >= 100m } => 0m, // relational inside: C# 9
{ Country: "US", Express: true } => 15m,
{ Country: "US" } => 5m,
{ Express: true } => 30m,
null => throw new ArgumentNullException(nameof(order)),
_ => 12m
};
Toda propriedade listada precisa corresponder para o braço corresponder. Um padrão de propriedade vazio { } corresponde a qualquer valor não nulo. O C# 10 adicionou padrões de propriedade estendidos para membros aninhados: { Customer.Address.Country: "US" } em vez de { Customer: { Address: { Country: "US" } } }.
Padrões de tupla
Fazer switch sobre uma tupla corresponde a vários valores ao mesmo tempo, que é a forma limpa de expressar uma tabela de decisão:
// C# 9 and later (the 'or' pattern)
string Winner(string a, string b) => (a, b) switch
{
("rock", "scissors") or ("scissors", "paper") or ("paper", "rock") => "player 1",
var (x, y) when x == y => "draw",
_ => "player 2"
};
var (x, y) desconstrói a tupla em duas variáveis que a guarda when pode comparar. No C# 7, o substituto usual é combinar os valores em uma única chave, ou usar um dicionário de resultados:
Saída:
player 1
player 2
draw
Padrões de tipo e when
Um braço pode testar o tipo em tempo de execução e vincular uma variável, e uma cláusula when adiciona qualquer condição que a sintaxe de padrões não consegue expressar:
// C# 8 and later
double Area(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r when r.Width == r.Height => r.Width * r.Width,
Rectangle r => r.Width * r.Height,
null => throw new ArgumentNullException(nameof(shape)),
_ => throw new NotSupportedException(shape.GetType().Name)
};
A versão em C# 7 usa o padrão de tipo is em uma cadeia de if, que vincula a variável da mesma forma:
Saída:
Circle: 12.57
Rectangle: 13.50
Exaustividade e SwitchExpressionException
Uma expressão switch precisa produzir um valor para toda entrada. Quando o compilador consegue ver entradas que nenhum braço cobre, ele avisa:
warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '0' is not covered.
É um aviso, não um erro, então o código ainda compila. Se um valor sem correspondência chegar em tempo de execução, a expressão lança System.Runtime.CompilerServices.SwitchExpressionException, cuja mensagem termina com o valor sem correspondência (Unmatched value was 3.).
Enums têm um caso especial. Mesmo quando cada membro com nome tem um braço, uma variável enum pode guardar qualquer inteiro ((Size)3), então o compilador reporta CS8524 para "um valor de enum sem nome":
// C# 8 and later
enum Size { Small, Medium, Large }
string Code(Size s) => s switch
{
Size.Small => "S",
Size.Medium => "M",
Size.Large => "L",
// warning CS8524 without the next arm
_ => throw new ArgumentOutOfRangeException(nameof(s))
};
Adicione um braço _ explícito que lança uma exceção útil. Isso documenta a suposição, silencia o aviso e dá um erro mais claro que SwitchExpressionException se um valor inválido aparecer algum dia.
Um Dictionary como alternativa
Quando os braços são puros pares de constante para valor e a tabela pode crescer ou vir de configuração, um Dictionary faz o mesmo trabalho em qualquer versão de C# e pode ser montado em tempo de execução:
Saída:
19.00
11.50
0
Um país desconhecido recebe a taxa 0m. O produto de dois decimal tem tantas casas decimais quanto os dois operandos somados, e é por isso que 100m * 0.19m imprime 19.00 e a taxa zero imprime 0.
Expressão switch versus instrução switch
| instrução switch | expressão switch | |
|---|---|---|
| Disponível desde | C# 1 | C# 8 |
| Produz um valor | não | sim |
| Sintaxe por ramo | case X: ... break; | X => value, |
| Alternativa final | default: | _ => |
| Várias instruções por ramo | sim | não, chame um método |
| Aviso de exaustividade | não | sim (CS8509) |
| Sem correspondência em tempo de execução | nada executa | SwitchExpressionException |
As duas formas aceitam os mesmos padrões (tipos, constantes, propriedades, tuplas, relacionais), então aprender uma ensina a outra. A escolha é sobre a forma: um valor passa pela expressão, uma sequência de ações passa pela instrução.
Perguntas frequentes
O que é uma expressão switch em C#?
Uma expressão switch, adicionada no C# 8, resulta em um valor: string text = code switch { 200 => "OK", 404 => "Not Found", _ => "Unknown" };. A entrada vem antes da palavra-chave switch, cada braço é pattern => result, os braços são separados por vírgulas e não existem as palavras-chave case, break nem default.
Qual é o caso default em uma expressão switch em C#?
O padrão de descarte _ corresponde a qualquer coisa, então _ => "Unknown" como último braço faz o papel de default. Sem um braço genérico, o compilador avisa (CS8509) quando alguma entrada não está coberta, e um valor sem correspondência em tempo de execução lança SwitchExpressionException.
Como corresponder a vários valores em um braço de uma expressão switch?
Use o padrão or do C# 9: "sat" or "sun" => "weekend". Para intervalos, combine padrões relacionais com and: >= 13 and <= 19 => "teen". No C# 8 você listaria os valores como braços separados com o mesmo resultado.
Posso usar when em uma expressão switch em C#?
Sim. Um braço pode adicionar uma guarda depois do padrão: Order o when o.Total > 100 => 0m. O braço só corresponde se o padrão corresponder e a condição when for verdadeira; caso contrário, a avaliação continua no próximo braço.
Devo usar uma expressão switch ou uma instrução switch?
Use a expressão quando cada ramo só produz um valor, como mapear um status para um rótulo ou calcular um preço. Use a instrução quando os ramos executam várias instruções, têm efeitos colaterais ou precisam sair de um laço. A expressão também dá avisos de exaustividade que a instrução não dá.