Menu

Operatore ternario in C# (?:): sintassi, tipi, annidamento e ??

L'operatore condizionale di C# condition ? a : b sceglie uno tra due valori. Come viene valutato, perché entrambi i rami devono avere un tipo comune (CS0173) e il cast che lo risolve, ternari annidati, parentesi nell'interpolazione di stringhe, e quando ?? e ?. lo dicono meglio.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

L'operatore condizionale, di solito chiamato operatore ternario, sceglie tra due valori in un'unica espressione: condition ? valueIfTrue : valueIfFalse.

Output:

0 items, shipping 4.99
1 item, shipping 4.99
5 items, shipping 0

La condizione deve essere un bool. Dopo averla valutata, viene eseguito solo uno dei due rami; l'altro non viene mai valutato. Questo rende l'operatore sicuro per gli accessi protetti come list.Count > 0 ? list[0] : "empty", dove valutare list[0] su una lista vuota lancerebbe un'eccezione.

Ternario e if/else a confronto

Il ternario è un'espressione: produce un valore che assegni, passi o restituisci. Un'istruzione if esegue istruzioni. Questi due metodi fanno la stessa cosa:

// 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";

Usa il ternario quando entrambi i rami sono valori brevi dello stesso genere. Usa if quando un ramo esegue più istruzioni, ha effetti collaterali, oppure quando la condizione è così lunga che la riga intera non ci sta più comodamente.

Il ternario non può sostituire un if che esegue solo azioni. Questo non 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

Non esiste nemmeno un ternario senza else: ? e : sono sempre obbligatori, perché l'espressione deve avere un valore in entrambi i casi.

Entrambi i rami devono avere un tipo comune

Il compilatore ricava il tipo dell'intera espressione dai suoi due rami. Un ramo deve potersi convertire implicitamente nel tipo dell'altro. Quando nessuno dei due può farlo, ottieni 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'

La soluzione è fare il cast di un ramo al tipo che vuoi, così l'altro si converte in quello:

Output:

no discount
7
3.5

points / 2.0 : 0 non ha bisogno di cast: 0 è un int, che si converte implicitamente in double, quindi l'espressione è un double.

C# 9 ha aggiunto la tipizzazione in base alla destinazione: quando l'espressione è assegnata a un tipo dichiarato, il compilatore usa quel tipo. int? discount = isMember ? 10 : null; compila in C# 9 e successivi. Con var non c'è un tipo di destinazione, quindi var discount = isMember ? 10 : null; fallisce ancora in ogni versione.

Precedenza e parentesi

L'operatore condizionale ha una precedenza molto bassa, più bassa di +, == e &&. È comodo in a > b ? a : b, e sorprendente dentro una concatenazione di stringhe:

Console.WriteLine("Status: " + isActive ? "on" : "off");
// error CS0029: Cannot implicitly convert type 'string' to 'bool'

Il compilatore legge la condizione come "Status: " + isActive, cioè una stringa. Le parentesi attorno al ternario risolvono il problema: "Status: " + (isActive ? "on" : "off").

L'interpolazione di stringhe ha una sua trappola. Dentro { }, i due punti iniziano uno specificatore di formato ({price:F2}), quindi un ternario nudo confonde il parser. Racchiudilo tra parentesi:

Output:

2 users online
You have 1 unread message
Status: online

Ternari annidati

L'operatore è associativo a destra, quindi a ? x : b ? y : z si raggruppa come a ? x : (b ? y : z). Una piccola catena con una condizione per riga è leggibile:

Output:

512 B
20 KB
5 MB

Oltre due o tre livelli, o quando le condizioni verificano cose diverse, passa a if/else if o a una switch expression. L'annidamento dentro il ramo vero (a ? (b ? x : y) : z) è la versione che diventa difficile da seguire per prima.

?? e ?. sostituiscono i ternari più comuni

Molti ternari esistono solo per gestire null. C# ha operatori più brevi per questi casi:

TernarioForma più breveSignificato
name != null ? name : "guest"name ?? "guest"valore di ripiego quando è null
customer != null ? customer.Email : nullcustomer?.Emailaccesso a un membro che tollera il null
c != null && c.Address != null ? c.Address.City : "unknown"c?.Address?.City ?? "unknown"entrambi combinati
value != null ? value : throw new ArgumentNullException(...)value ?? throw new ArgumentNullException(...)fallire subito

Output:

Lena from Berlin
guest from unknown
guest from unknown

Anche un'espressione throw è consentita in uno dei due rami di un ternario: int age = input >= 0 ? input : throw new ArgumentOutOfRangeException(nameof(input));.

Il ref condizionale

Da C# 7.2, un ternario può scegliere tra due variabili invece che tra due valori, e puoi assegnare tramite il risultato:

// 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

Entrambi i rami devono essere ref a variabili dello stesso tipo. È raro nel codice applicativo; esiste soprattutto per il codice sensibile alle prestazioni che lavora con struct grandi o array senza copiarli.

Domande frequenti

Cos'è l'operatore ternario in C#?

È l'operatore condizionale condition ? valueIfTrue : valueIfFalse, l'unico operatore di C# con tre operandi. Valuta la condizione, poi valuta e restituisce esattamente uno dei due valori: string label = age >= 18 ? "adult" : "minor";.

Posso usare un ternario senza else in C#?

No. Sia la parte ? sia la parte : sono obbligatorie, perché l'espressione deve sempre produrre un valore. Se vuoi fare qualcosa solo quando una condizione è vera, usa un'istruzione if. Il ternario non può nemmeno stare da solo come istruzione: x > 0 ? A() : B(); è l'errore CS0201.

Perché ricevo "Type of conditional expression cannot be determined"?

I due rami non hanno un tipo comune, per esempio var x = found ? 42 : null; (int e null) oppure found ? 1 : "none". Fai il cast di un ramo in modo che un tipo esista: found ? (int?)42 : null. Da C# 9 funziona anche un tipo di destinazione dichiarato: int? x = found ? 42 : null; compila, ma con var fallisce ancora.

Come uso l'operatore ternario dentro l'interpolazione di stringhe?

Racchiudilo tra parentesi: $"{(count == 1 ? "item" : "items")}". Senza parentesi, i : vengono letti come l'inizio di uno specificatore di formato e il codice non compila.

Si possono annidare gli operatori ternari in C#?

Sì. L'operatore è associativo a destra, quindi a ? x : b ? y : z significa a ? x : (b ? y : z). Un livello di annidamento disposto su righe separate si legge bene; catene più profonde sono più chiare come catena if/else if o come switch expression.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA