Menu

Opérateur ternaire en C# (?:) : syntaxe, types, imbrication et ??

L'opérateur conditionnel de C#, condition ? a : b, choisit l'une de deux valeurs. Comment il s'évalue, pourquoi les deux branches ont besoin d'un type commun (CS0173) et le cast qui corrige l'erreur, les ternaires imbriqués, les parenthèses dans l'interpolation, et quand ?? et ?. l'expriment mieux.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

L'opérateur conditionnel, qu'on appelle en général l'opérateur ternaire, choisit entre deux valeurs dans une seule expression : condition ? valueIfTrue : valueIfFalse.

Sortie :

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

La condition doit être un bool. Une fois qu'elle est évaluée, une seule des deux branches s'exécute ; l'autre n'est jamais évaluée. L'opérateur est donc sûr pour un accès protégé comme list.Count > 0 ? list[0] : "empty", où évaluer list[0] sur une liste vide lèverait une exception.

Ternaire ou if/else

Le ternaire est une expression : il produit une valeur que l'on affecte, passe ou renvoie. Une instruction if exécute des instructions. Ces deux méthodes font la même chose :

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

Utilisez le ternaire quand les deux branches sont des valeurs courtes de même nature. Utilisez if quand une branche exécute plusieurs instructions, a des effets de bord, ou quand la condition est assez longue pour que la ligne ne tienne plus confortablement.

Le ternaire ne peut pas remplacer un if qui ne fait qu'exécuter des actions. Ceci ne compile pas :

stock > 0 ? Ship(order) : Refund(order);
// error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement

Il n'existe pas non plus de ternaire sans else : ? et : sont toujours obligatoires, car l'expression doit avoir une valeur dans les deux cas.

Les deux branches ont besoin d'un type commun

Le compilateur détermine le type de l'expression entière à partir de ses deux branches. L'une des branches doit se convertir implicitement vers le type de l'autre. Quand aucune ne le peut, vous obtenez 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 correction consiste à caster une branche vers le type voulu, pour que l'autre s'y convertisse :

Sortie :

no discount
7
3.5

points / 2.0 : 0 n'a pas besoin de cast : 0 est un int, qui se convertit implicitement en double, donc l'expression est un double.

C# 9 a ajouté le typage par la cible : quand l'expression est affectée à un type déclaré, le compilateur utilise ce type. int? discount = isMember ? 10 : null; compile en C# 9 et plus. Avec var, il n'y a pas de type cible, donc var discount = isMember ? 10 : null; échoue toujours, quelle que soit la version.

Priorité et parenthèses

L'opérateur conditionnel a une priorité très basse, inférieure à celle de +, == et &&. C'est pratique dans a > b ? a : b, et surprenant dans une concaténation de chaînes :

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

Le compilateur lit la condition comme "Status: " + isActive, une chaîne. Des parenthèses autour du ternaire corrigent le problème : "Status: " + (isActive ? "on" : "off").

L'interpolation de chaînes a son propre piège. Dans { }, les deux-points commencent un spécificateur de format ({price:F2}), donc un ternaire nu perturbe l'analyseur. Entourez-le de parenthèses :

Sortie :

2 users online
You have 1 unread message
Status: online

Ternaires imbriqués

L'opérateur est associatif à droite, donc a ? x : b ? y : z se groupe en a ? x : (b ? y : z). Une petite chaîne disposée avec une condition par ligne reste lisible :

Sortie :

512 B
20 KB
5 MB

Au-delà de deux ou trois niveaux, ou quand les conditions testent des choses différentes, passez à if/else if ou à une expression switch. L'imbrication dans la branche vraie (a ? (b ? x : y) : z) est la version qui devient la première difficile à suivre.

?? et ?. remplacent les ternaires courants

Beaucoup de ternaires n'existent que pour gérer null. C# a des opérateurs plus courts pour ces cas :

TernaireForme plus courteSignification
name != null ? name : "guest"name ?? "guest"valeur de repli quand null
customer != null ? customer.Email : nullcustomer?.Emailaccès à un membre qui tolère null
c != null && c.Address != null ? c.Address.City : "unknown"c?.Address?.City ?? "unknown"les deux combinés
value != null ? value : throw new ArgumentNullException(...)value ?? throw new ArgumentNullException(...)échouer tôt

Sortie :

Lena from Berlin
guest from unknown
guest from unknown

Une expression throw est aussi autorisée dans l'une ou l'autre branche d'un ternaire : int age = input >= 0 ? input : throw new ArgumentOutOfRangeException(nameof(input));.

Le ternaire ref

Depuis C# 7.2, un ternaire peut choisir entre deux variables plutôt qu'entre deux valeurs, et vous pouvez affecter à travers le résultat :

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

Les deux branches doivent être des ref vers des variables du même type. C'est rare dans le code applicatif ; cela existe surtout pour le code sensible aux performances qui manipule de grandes structs ou des tableaux sans les copier.

Questions fréquentes

Qu'est-ce que l'opérateur ternaire en C# ?

C'est l'opérateur conditionnel condition ? valueIfTrue : valueIfFalse, le seul opérateur C# à trois opérandes. Il évalue la condition, puis évalue et renvoie exactement l'une des deux valeurs : string label = age >= 18 ? "adult" : "minor";.

Peut-on utiliser un ternaire sans else en C# ?

Non. Les parties ? et : sont toutes deux obligatoires, car l'expression doit toujours produire une valeur. Si vous voulez seulement agir quand une condition est vraie, utilisez une instruction if. Le ternaire ne peut pas non plus constituer une instruction à lui seul : x > 0 ? A() : B(); donne l'erreur CS0201.

Pourquoi obtient-on « Type of conditional expression cannot be determined » ?

Les deux branches n'ont pas de type commun, par exemple var x = found ? 42 : null; (int et null) ou found ? 1 : "none". Castez une branche pour qu'un type existe : found ? (int?)42 : null. Depuis C# 9, un type cible déclaré fonctionne aussi : int? x = found ? 42 : null; compile, mais var échoue toujours.

Comment utiliser l'opérateur ternaire dans une interpolation de chaîne ?

Entourez-le de parenthèses : $"{(count == 1 ? "item" : "items")}". Sans elles, les : sont lus comme le début d'un spécificateur de format et le code ne compile pas.

Peut-on imbriquer des opérateurs ternaires en C# ?

Oui. L'opérateur est associatif à droite, donc a ? x : b ? y : z signifie a ? x : (b ? y : z). Un niveau d'imbrication disposé sur des lignes séparées se lit bien ; les chaînes plus profondes sont plus claires sous forme de chaîne if/else if ou d'expression switch.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER