Menu

Expressão switch em C#: sintaxe, padrões, descarte e when

A expressão switch do C# 8 transforma um valor em um resultado com braços de padrão, sem case e break. Sintaxe, o descarte _, padrões de propriedade, de tupla e relacionais, and/or/not, guardas when, avisos de exaustividade e SwitchExpressionException, com equivalentes executáveis em C# 7.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

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 void como Console.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 switchexpressão switch
Disponível desdeC# 1C# 8
Produz um valornãosim
Sintaxe por ramocase X: ... break;X => value,
Alternativa finaldefault:_ =>
Várias instruções por ramosimnão, chame um método
Aviso de exaustividadenãosim (CS8509)
Sem correspondência em tempo de execuçãonada executaSwitchExpressionException

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á.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR