O operador condicional, normalmente chamado de operador ternário, escolhe entre dois valores em uma única expressão: condition ? valueIfTrue : valueIfFalse.
Saída:
0 items, shipping 4.99
1 item, shipping 4.99
5 items, shipping 0
A condição precisa ser um bool. Depois que ela é avaliada, só um dos dois ramos executa; o outro nunca é avaliado. Isso torna o operador seguro para acessos protegidos como list.Count > 0 ? list[0] : "empty", em que avaliar list[0] em uma lista vazia lançaria uma exceção.
Ternário versus if/else
O ternário é uma expressão: ele produz um valor que você atribui, passa como argumento ou retorna. Uma instrução if executa instruções. Estes dois métodos fazem a mesma coisa:
// with if/else
static string Status(int stock)
{
if (stock > 0)
return "in stock";
else
return "sold out";
}
// with the ternary
static string Status(int stock) => stock > 0 ? "in stock" : "sold out";
Use o ternário quando os dois ramos forem valores curtos do mesmo tipo. Use if quando um ramo executa várias instruções, tem efeitos colaterais, ou quando a condição é tão longa que a linha inteira deixa de caber com folga.
O ternário não substitui um if que só executa ações. Isto não compila:
stock > 0 ? Ship(order) : Refund(order);
// error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement
Também não existe ternário sem else: ? e : são sempre obrigatórios, porque a expressão precisa ter um valor nos dois casos.
Os dois ramos precisam de um tipo comum
O compilador deduz o tipo da expressão inteira a partir dos dois ramos. Um ramo precisa ser convertido implicitamente para o tipo do outro. Quando nenhum é, você recebe o CS0173:
var discount = isMember ? 10 : null;
// error CS0173: Type of conditional expression cannot be determined because
// there is no implicit conversion between 'int' and '<null>'
var label = inStock ? 1 : "none";
// error CS0173: ... between 'int' and 'string'
A solução é fazer o cast de um ramo para o tipo que você quer, para que o outro seja convertido para ele:
Saída:
no discount
7
3.5
points / 2.0 : 0 não precisa de cast: 0 é um int, que é convertido implicitamente para double, então a expressão é um double.
O C# 9 adicionou a tipagem por destino: quando a expressão é atribuída a um tipo declarado, o compilador usa esse tipo. int? discount = isMember ? 10 : null; compila no C# 9 em diante. Com var não há tipo de destino, então var discount = isMember ? 10 : null; continua falhando em todas as versões.
Precedência e parênteses
O operador condicional tem precedência muito baixa, menor que +, == e &&. Isso é conveniente em a > b ? a : b, e surpreendente dentro de uma concatenação de strings:
Console.WriteLine("Status: " + isActive ? "on" : "off");
// error CS0029: Cannot implicitly convert type 'string' to 'bool'
O compilador lê a condição como "Status: " + isActive, uma string. Parênteses em volta do ternário resolvem: "Status: " + (isActive ? "on" : "off").
A interpolação de strings tem sua própria armadilha. Dentro de { }, dois pontos iniciam um especificador de formato ({price:F2}), então um ternário sem parênteses confunde o parser. Coloque-o entre parênteses:
Saída:
2 users online
You have 1 unread message
Status: online
Ternários aninhados
O operador é associativo à direita, então a ? x : b ? y : z é agrupado como a ? x : (b ? y : z). Uma cadeia pequena com uma condição por linha fica legível:
Saída:
512 B
20 KB
5 MB
Passando de dois ou três níveis, ou quando as condições testam coisas diferentes, troque para if/else if ou uma expressão switch. Aninhar dentro do ramo verdadeiro (a ? (b ? x : y) : z) é a versão que primeiro fica difícil de acompanhar.
?? e ?. substituem ternários comuns
Muitos ternários existem só para tratar null. C# tem operadores mais curtos para isso:
| Ternário | Forma mais curta | Significado |
|---|---|---|
name != null ? name : "guest" | name ?? "guest" | valor alternativo quando é null |
customer != null ? customer.Email : null | customer?.Email | acesso a membro que tolera null |
c != null && c.Address != null ? c.Address.City : "unknown" | c?.Address?.City ?? "unknown" | os dois combinados |
value != null ? value : throw new ArgumentNullException(...) | value ?? throw new ArgumentNullException(...) | falhar cedo |
Saída:
Lena from Berlin
guest from unknown
guest from unknown
Uma expressão throw também é permitida em qualquer um dos ramos de um ternário: int age = input >= 0 ? input : throw new ArgumentOutOfRangeException(nameof(input));.
O condicional ref
Desde o C# 7.2, um ternário pode escolher entre duas variáveis em vez de dois valores, e você pode atribuir por meio do resultado:
// C# 7.2 and later
int wins = 0, losses = 0;
bool won = true;
ref int counter = ref (won ? ref wins : ref losses);
counter++; // increments wins
Os dois ramos precisam ser ref para variáveis do mesmo tipo. É raro em código de aplicação; existe principalmente para código sensível a desempenho que trabalha com structs ou arrays grandes sem copiá-los.
Perguntas frequentes
O que é o operador ternário em C#?
É o operador condicional condition ? valueIfTrue : valueIfFalse, o único operador de C# com três operandos. Ele avalia a condição e depois avalia e retorna exatamente um dos dois valores: string label = age >= 18 ? "adult" : "minor";.
Posso usar um ternário sem else em C#?
Não. As partes ? e : são obrigatórias, porque a expressão sempre precisa produzir um valor. Se você só quer fazer algo quando uma condição vale, use uma instrução if. O ternário também não pode ficar sozinho como instrução: x > 0 ? A() : B(); é o erro CS0201.
Por que recebo "Type of conditional expression cannot be determined"?
Os dois ramos não têm um tipo comum, por exemplo var x = found ? 42 : null; (int e null) ou found ? 1 : "none". Faça o cast de um ramo para que exista um tipo: found ? (int?)42 : null. Desde o C# 9, um tipo de destino declarado também funciona: int? x = found ? 42 : null; compila, mas var ainda falha.
Como usar o operador ternário dentro da interpolação de strings?
Coloque-o entre parênteses: $"{(count == 1 ? "item" : "items")}". Sem eles, o : é lido como o início de um especificador de formato e o código não compila.
Dá para aninhar operadores ternários em C#?
Sim. O operador é associativo à direita, então a ? x : b ? y : z significa a ? x : (b ? y : z). Um nível de aninhamento distribuído em linhas separadas fica legível; cadeias mais profundas ficam mais claras como uma cadeia if/else if ou uma expressão switch.