Menu

Operador ternário em C# (?:): sintaxe, tipos, aninhamento e ??

O operador condicional de C# condition ? a : b escolhe um de dois valores. Como ele é avaliado, por que os dois ramos precisam de um tipo comum (CS0173) e o cast que resolve, ternários aninhados, parênteses na interpolação de strings e quando ?? e ?. dizem isso melhor.

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

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árioForma mais curtaSignificado
name != null ? name : "guest"name ?? "guest"valor alternativo quando é null
customer != null ? customer.Email : nullcustomer?.Emailacesso 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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR