Условный оператор, который обычно называют тернарным, выбирает между двумя значениями в одном выражении: condition ? valueIfTrue : valueIfFalse.
Вывод:
0 items, shipping 4.99
1 item, shipping 4.99
5 items, shipping 0
Условие должно иметь тип bool. После его вычисления выполняется только одна из двух ветвей; другая не вычисляется никогда. Поэтому оператор безопасен для защищённого доступа вроде list.Count > 0 ? list[0] : "empty", где вычисление list[0] для пустого списка выбросило бы исключение.
Тернарный оператор и if/else
Тернарный оператор это выражение: он выдаёт значение, которое вы присваиваете, передаёте или возвращаете. Оператор if выполняет операторы. Эти два метода делают одно и то же:
// 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";
Используйте тернарный оператор, когда обе ветви это короткие значения одного рода. Используйте if, когда ветвь выполняет несколько операторов, имеет побочные эффекты или когда условие настолько длинное, что вся строка уже неудобно помещается.
Тернарный оператор не может заменить if, который только выполняет действия. Это не компилируется:
stock > 0 ? Ship(order) : Refund(order);
// error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement
Тернарного оператора без else тоже нет: ? и : всегда обязательны, потому что выражение должно иметь значение в обоих случаях.
Обеим ветвям нужен общий тип
Компилятор выводит тип всего выражения из двух его ветвей. Одна ветвь должна неявно преобразовываться к типу другой. Если ни одна не преобразуется, вы получаете 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'
Исправление: приведите одну ветвь к нужному типу, чтобы другая к нему преобразовалась:
Вывод:
no discount
7
3.5
points / 2.0 : 0 не требует приведения: 0 имеет тип int, который неявно преобразуется в double, поэтому выражение имеет тип double.
C# 9 добавил целевую типизацию: когда выражение присваивается переменной объявленного типа, компилятор использует этот тип. int? discount = isMember ? 10 : null; компилируется в C# 9 и новее. С var целевого типа нет, поэтому var discount = isMember ? 10 : null; по-прежнему не компилируется ни в одной версии.
Приоритет и скобки
Условный оператор имеет очень низкий приоритет, ниже, чем у +, == и &&. Это удобно в a > b ? a : b и неожиданно внутри конкатенации строк:
Console.WriteLine("Status: " + isActive ? "on" : "off");
// error CS0029: Cannot implicitly convert type 'string' to 'bool'
Компилятор читает условие как "Status: " + isActive, то есть строку. Скобки вокруг тернарного оператора это исправляют: "Status: " + (isActive ? "on" : "off").
У интерполяции строк своя ловушка. Внутри { } двоеточие начинает спецификатор формата ({price:F2}), поэтому голый тернарный оператор сбивает с толку парсер. Заключите его в скобки:
Вывод:
2 users online
You have 1 unread message
Status: online
Вложенные тернарные операторы
Оператор правоассоциативен, поэтому a ? x : b ? y : z группируется как a ? x : (b ? y : z). Небольшая цепочка, где каждое условие на своей строке, читается хорошо:
Вывод:
512 B
20 KB
5 MB
После двух или трёх уровней или когда условия проверяют разные вещи, переходите на if/else if или switch-выражение. Вложенность в ветвь true (a ? (b ? x : y) : z) это вариант, который первым становится трудным для понимания.
?? и ?. заменяют частые тернарные операторы
Многие тернарные операторы существуют только ради обработки null. Для них в C# есть более короткие операторы:
| Тернарный оператор | Короткая форма | Смысл |
|---|---|---|
name != null ? name : "guest" | name ?? "guest" | запасное значение при null |
customer != null ? customer.Email : null | customer?.Email | доступ к члену, допускающий null |
c != null && c.Address != null ? c.Address.City : "unknown" | c?.Address?.City ?? "unknown" | оба вместе |
value != null ? value : throw new ArgumentNullException(...) | value ?? throw new ArgumentNullException(...) | быстрый отказ |
Вывод:
Lena from Berlin
guest from unknown
guest from unknown
Выражение throw тоже допустимо в любой из ветвей тернарного оператора: int age = input >= 0 ? input : throw new ArgumentOutOfRangeException(nameof(input));.
Условное выражение ref
Начиная с C# 7.2 тернарный оператор может выбирать между двумя переменными, а не двумя значениями, и через результат можно присваивать:
// 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
Обе ветви должны быть ref на переменные одного типа. В прикладном коде это встречается редко; возможность существует в основном для кода, чувствительного к производительности, который работает с большими структурами или массивами без копирования.
Часто задаваемые вопросы
Что такое тернарный оператор в C#?
Это условный оператор condition ? valueIfTrue : valueIfFalse, единственный оператор C# с тремя операндами. Он вычисляет условие, затем вычисляет и возвращает ровно одно из двух значений: string label = age >= 18 ? "adult" : "minor";.
Можно ли использовать тернарный оператор без else в C#?
Нет. Обе части, ? и :, обязательны, потому что выражение всегда должно выдавать значение. Если нужно что-то сделать только при выполнении условия, используйте оператор if. Тернарный оператор также не может стоять отдельно как оператор: x > 0 ? A() : B(); это ошибка CS0201.
Почему возникает ошибка «Type of conditional expression cannot be determined»?
У двух ветвей нет общего типа, например var x = found ? 42 : null; (int и null) или found ? 1 : "none". Приведите одну ветвь, чтобы общий тип появился: found ? (int?)42 : null. Начиная с C# 9 работает и объявленный целевой тип: int? x = found ? 42 : null; компилируется, а с var по-прежнему ошибка.
Как использовать тернарный оператор внутри интерполяции строк?
Заключите его в скобки: $"{(count == 1 ? "item" : "items")}". Без них : читается как начало спецификатора формата, и код не компилируется.
Можно ли вкладывать тернарные операторы в C#?
Да. Оператор правоассоциативен, поэтому a ? x : b ? y : z означает a ? x : (b ? y : z). Один уровень вложенности, разнесённый по отдельным строкам, читается нормально; более глубокие цепочки понятнее в виде if/else if или switch-выражения.