C# Operador Ternário (? :)

Na aula de if e else, o programa da Ana mostrou "Faltam 1 anos para a carteira". Para escolher entre "ano" e "anos", daria para escrever um if/else de oito linhas. O operador ternário faz isso numa linha: ele faz uma pergunta e devolve um de dois valores. Chama-se "ternário" porque é o único operador do C# com três partes: a condição, o valor do "sim" e o valor do "não".

Você vai aprender
  • Como escrever condição ? valorSeSim : valorSeNão.
  • Por que ele é o mesmo que um if/else que dá valor a uma variável.
  • Como usá-lo dentro de $"…" (com parênteses, senão: CS8361).
  • Por que os dois valores precisam ser do mesmo tipo (CS0173).
  • Quando não usar: para ações (CS0201) e em cadeias longas.

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: "se chover, guarda-chuva; senão, boné"

De manhã, a Ana olha pela janela e decide o que levar: "se estiver chovendo, o guarda-chuva; senão, o boné". Ela não faz duas coisas diferentes, ela escolhe uma coisa para levar. A resposta da pergunta é um valor.

O if escolhe qual trecho de código roda. O ternário escolhe qual valor usar. Em C#, a frase da Ana fica assim:

string levar = chovendo ? "guarda-chuva" : "boné";

Leia em voz alta: "levar recebe: está chovendo? então guarda-chuva; senão, boné".


1. O primeiro ternário

int idade = 17;
int faltam = 18 - idade;
string palavra = faltam == 1 ? "ano" : "anos";
Console.WriteLine($"Faltam {faltam} {palavra} para a carteira.");
$ dotnet run ano.cs
Faltam 1 ano para a carteira.        « faltam == 1 é true: escolheu "ano"
faltam == 1
A condição, que tem de dar bool, como no if. Aqui os parênteses são opcionais.
?
"Então…": separa a pergunta das respostas.
"ano"
O valor que a expressão inteira vale quando a condição é true.
:
"Senão…": separa as duas respostas.
"anos"
O valor quando a condição é false.
string palavra =
O resultado (um dos dois textos) é guardado na variável, como o resultado de qualquer conta.

Mude a idade para 15 e a saída vira Faltam 3 anos para a carteira.. Só um dos dois valores é calculado: o C# olha a condição primeiro e depois avalia só o lado escolhido.

O mesmo programa com if/else

O ternário não faz nada que um if não faça. É só mais curto. Este programa é equivalente:

int faltam = 3;
string palavra;
if (faltam == 1)
{
    palavra = "ano";
}
else
{
    palavra = "anos";
}
Console.WriteLine($"Faltam {faltam} {palavra}.");
$ dotnet run ifelse_valor.cs
Faltam 3 anos.

Nove linhas contra uma. Quando a decisão é só "qual valor guardar", o ternário deixa o código mais limpo.

Missão: complete a linha de maior com um ternário que escolha o maior entre a e b. Depois troque os valores (por exemplo, a = 50) e confira.

O maior é 30
int a = 12;
int b = 30;
int maior = a;  // troque por um ternário
Console.WriteLine($"O maior é {maior}");
int a = 12;
int b = 30;
int maior = a > b ? a : b;
Console.WriteLine($"O maior é {maior}");

(Para o maior de dois números, o .NET também tem Math.Max(a, b), que você vai ver em Matemática.)


2. Dentro de $"…": use parênteses

É muito comum querer o ternário direto na interpolação. Mas veja o que acontece:

int n = 7;
Console.WriteLine($"{n} é {n % 2 == 0 ? "par" : "ímpar"}");
$ dotnet run interp.cs
/home/ana/cs/interp.cs(2,26): error CS8076: Delimitador de fechamento ausente '}' para expressão interpolada iniciada com '{'.
/home/ana/cs/interp.cs(2,28): error CS8361: Uma expressão condicional não pode ser usada diretamente em uma interpolação de cadeia de caracteres porque ‘:’ encerra a interpolação. Use parênteses na expressão condicional.
/home/ana/cs/interp.cs(2,50): error CS1003: Erro de sintaxe, "," esperado
/home/ana/cs/interp.cs(2,55): error CS1003: Erro de sintaxe, "," esperado

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O editor do guia mostra os mesmos quatro erros. Como são erros de sintaxe, o compilador para neles e não chega a procurar outros.

Lembra do {total:N2} da aula Formatar Números? Dentro das chaves de uma interpolação, o : quer dizer "agora vem o formato". O compilador vê o : do ternário e acha que é o começo de um formato. O erro CS8361 explica isso e já diz o conserto: "Use parênteses na expressão condicional".

int n = 7;
Console.WriteLine($"{n} é {(n % 2 == 0 ? "par" : "ímpar")}");
$ dotnet run interp2.cs
7 é ímpar

Com os parênteses, o : fica "protegido" lá dentro, e o compilador entende que ele é parte do ternário.


3. Os dois lados precisam combinar

O resultado de um ternário tem um tipo só. Por isso, os dois valores precisam ser do mesmo tipo, ou de tipos que o C# saiba converter um no outro. A Ana quis mostrar a quantidade em estoque ou a palavra "esgotado":

int estoque = 0;
var mostrar = estoque > 0 ? estoque : "esgotado";
Console.WriteLine(mostrar);
$ dotnet run tipos.cs
/home/ana/cs/tipos.cs(2,15): error CS0173: Tipo de expressão condicional não pode ser determinado porque não há conversão implícita entre "int" e "string"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

Um lado é int (número), o outro é string (texto), e o compilador não sabe qual deve ser o tipo de mostrar. O conserto é deixar os dois lados como texto, com ToString():

int estoque = 0;
string mostrar = estoque > 0 ? estoque.ToString() : "esgotado";
Console.WriteLine(mostrar);
$ dotnet run tipos2.cs
esgotado

Tipos numéricos "parentes" combinam sem problema: em decimal frete = total >= 100m ? 0m : 15m; os dois lados são decimal, e em double x = ok ? 1 : 2.5; o 1 vira double sozinho (aula Conversão de Tipos).


4. Ternário é para valores, não para ações

Quem acabou de aprender o ternário tenta usá-lo no lugar de qualquer if:

int nota = 8;
nota >= 6 ? Console.WriteLine("Aprovada") : Console.WriteLine("Reprovada");
$ dotnet run acao.cs
/home/ana/cs/acao.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.

O ternário produz um valor, e uma linha que só produz um valor, sem guardar nem usar, não faz nada. Por isso o C# não aceita um ternário sozinho como instrução (em inglês: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement). Além disso, Console.WriteLine não devolve valor nenhum. Há duas formas certas:

// 1. if/else: para fazer coisas diferentes
if (nota >= 6)
{
    Console.WriteLine("Aprovada");
}
else
{
    Console.WriteLine("Reprovada");
}

// 2. ternário: para escolher o valor, e uma ação só
Console.WriteLine(nota >= 6 ? "Aprovada" : "Reprovada");

A segunda forma funciona porque o ternário agora está dentro de uma chamada: ele escolhe o texto, e o Console.WriteLine mostra.


5. Cuidado com a precedência

O ternário tem precedência muito baixa: quase tudo é calculado antes dele (aula Precedência). Isso pega quem junta texto com +:

bool logado = true;
string saudacao = "Olá, " + logado ? "Ana" : "visitante";
Console.WriteLine(saudacao);
$ dotnet run precedencia.cs
/home/ana/cs/precedencia.cs(2,19): error CS0029: Não é possível converter implicitamente tipo "string" em "bool"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O + roda primeiro: "Olá, " + logado vira o texto "Olá, True". Depois, esse texto vira a condição do ternário, e uma condição tem de ser bool. Daí o CS0029, apontando a coluna 19, o começo de "Olá, ". Conserto: parênteses em volta do ternário.

bool logado = true;
string saudacao = "Olá, " + (logado ? "Ana" : "visitante");
Console.WriteLine(saudacao);
$ dotnet run precedencia2.cs
Olá, Ana
Regra prática

Se o ternário não é a única coisa do lado direito do =, ponha parênteses em volta dele. Nunca atrapalha, e evita os erros desta seção e da seção 2.


6. Ternários encadeados

O "valor do senão" pode ser outro ternário. Assim dá para escolher entre mais de dois valores, como a cadeia de else if:

double nota = 7.5;
string conceito = nota >= 9 ? "A"
                : nota >= 7 ? "B"
                : nota >= 5 ? "C"
                : "D";
Console.WriteLine($"Conceito {conceito}");
$ dotnet run encadeado.cs
Conceito B

Funciona, e escrito assim, um por linha, até dá para ler. Mas numa linha só (nota >= 9 ? "A" : nota >= 7 ? "B" : nota >= 5 ? "C" : "D"), vira um quebra-cabeça. Para escolher um valor entre várias faixas, o C# moderno tem uma ferramenta feita para isso, a expressão switch:

string conceito = nota switch
{
    >= 9 => "A",
    >= 7 => "B",
    >= 5 => "C",
    _ => "D"
};

Qual usar?

SituaçãoUseExemplo
Escolher entre dois valoresTernáriovar palavra = n == 1 ? "item" : "itens";
Fazer coisas diferentes (várias linhas, ações)if/elseif (erro) { avisar(); parar(); } else { … }
Escolher entre três ou mais valoresExpressão switchnota switch { >= 9 => "A", … }
Valor padrão quando algo é nullOperador ??nome ?? "visitante" (aula Operadores ??, ?. e ??=)

Quando dá errado

error CS8361: Uma expressão condicional não pode ser usada diretamente em uma interpolação…

O que significa: você pôs um ternário dentro de { } num $"…", e o : dele foi lido como o começo de um formato. Vem acompanhado de CS8076 e CS1003, que são consequência.

Como consertar: parênteses em volta do ternário: {(cond ? "a" : "b")}.

error CS0173: Tipo de expressão condicional não pode ser determinado…

O que significa: os dois valores têm tipos que não combinam, como int e string.

Como consertar: deixe os dois do mesmo tipo: estoque.ToString() no lugar de estoque, ou "0" no lugar de 0.

error CS0201: Somente as expressões de atribuição, chamada… podem ser utilizadas como uma instrução

O que significa: um ternário sozinho na linha, sem guardar o resultado em lugar nenhum, normalmente com uma ação em cada lado.

Como consertar: use if/else, ou ponha o ternário dentro da ação: Console.WriteLine(ok ? "sim" : "não");.

error CS0029: Não é possível converter implicitamente tipo "string" em "bool" num ternário

O que significa: algo antes do ? (normalmente um +) foi calculado primeiro e virou a condição.

Como consertar: parênteses em volta do ternário: "Olá, " + (logado ? "Ana" : "visitante").

error CS1003: Erro de sintaxe, ":" esperado
$ cat semtres.cs
int n = 7;
string tipo = n % 2 == 0 ? "par";
Console.WriteLine(tipo);
$ dotnet run semtres.cs
/home/ana/cs/semtres.cs(2,33): error CS1003: Erro de sintaxe, ":" esperado
/home/ana/cs/semtres.cs(2,33): error CS1525: Termo de expressão inválido ";"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que significa: o ternário está sem a terceira parte. Diferente do if, que pode ficar sem else, o ternário sempre precisa do valor do "senão": a variável tem de receber alguma coisa nos dois casos.

Como consertar: n % 2 == 0 ? "par" : "ímpar".


Referência rápida

FormaO que faz
cond ? a : bVale a se cond for true; senão, vale b. Só o lado escolhido é calculado.
var x = cond ? a : b;O mesmo que um if/else que dá valor a x.
$"{(cond ? a : b)}"Dentro da interpolação, sempre com parênteses.
"texto" + (cond ? a : b)Com + ou outra conta, parênteses em volta.
c1 ? a : c2 ? b : cEncadeado: funciona, mas prefira a expressão switch.
Tipos de a e bPrecisam combinar (CS0173 se não).

Exercícios

1. Complete o ternário

Complete os dois símbolos que faltam:

string status = idade >= 18 "maior" "menor";

2. Qual o valor?

Com int x = 4;, quanto vale x > 5 ? 10 : 20?

Resposta:

3. Na interpolação

Complete o que falta para esta linha compilar (dois caracteres, um de cada lado):

Console.WriteLine($"Status: {ok ? "ligado" : "desligado"}");

4. Por que var x = ok ? 1 : "um"; não compila?

5. Qual destas linhas é um bom uso do ternário?

Missão final: o carrinho da lojinha

Missão: o carrinho da Ana tem 1 item e R$ 120 de compras. O programa está com dois erros: mostra "1 itens" e cobra frete de quem passou de R$ 100. Use dois ternários:

  • um que escolha "item" ou "itens" (dentro da interpolação, não esqueça os parênteses);
  • um que dê ao frete o valor 0m quando total >= 100m, e 15m senão.

A linha do textoFrete já está pronta (e é um ternário também). A saída deve ser:

Você tem 1 item no carrinho.
Frete: grátis
int itens = 1;
decimal total = 120m;
Console.WriteLine($"Você tem {itens} itens no carrinho.");

decimal frete = 15m;
string textoFrete = frete == 0m ? "grátis" : $"R$ {frete:N2}";
Console.WriteLine($"Frete: {textoFrete}");
int itens = 1;
decimal total = 120m;
Console.WriteLine($"Você tem {itens} {(itens == 1 ? "item" : "itens")} no carrinho.");

decimal frete = total >= 100m ? 0m : 15m;
string textoFrete = frete == 0m ? "grátis" : $"R$ {frete:N2}";
Console.WriteLine($"Frete: {textoFrete}");

Depois, troque para itens = 3 e total = 59.90m: a saída deve virar Você tem 3 itens no carrinho. e Frete: R$ 15,00.


Resumo

  • condição ? valorSeSim : valorSeNão escolhe um de dois valores. É o único operador do C# com três partes.
  • É o mesmo que um if/else que dá valor a uma variável, só que numa linha. Só o lado escolhido é calculado.
  • Dentro de $"…", ponha parênteses (CS8361). Junto com +, também (CS0029).
  • Os dois valores precisam ter tipos que combinem (CS0173), e a terceira parte é obrigatória.
  • Para ações, use if/else (CS0201). Para três ou mais valores, prefira a expressão switch.