Menu

C# 三元运算符(?:):语法、类型、嵌套与 ??

C# 的条件运算符 condition ? a : b 会从两个值中选出一个。它如何求值、为什么两个分支需要共同的类型(CS0173)以及修复它的强制转换、嵌套三元表达式、字符串插值中的括号,以及什么时候用 ?? 和 ?. 表达得更好。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

条件运算符通常叫作三元运算符,它在一个表达式里从两个值中选出一个: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 表达式。在真分支里嵌套(a ? (b ? x : y) : z)是最先变得难以理解的写法。

用 ?? 和 ?. 替代常见的三元表达式

很多三元表达式只是为了处理 null。C# 为这些情况提供了更简短的运算符:

三元表达式更简短的写法含义
name != null ? name : "guest"name ?? "guest"为 null 时使用备用值
customer != null ? customer.Email : nullcustomer?.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";。

C# 中三元运算符可以不写 else 部分吗?

不可以。? 和 : 两部分都是必需的,因为这个表达式必须始终产生一个值。如果你只想在条件成立时做某件事,请用 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 表达式会更清楚。

Coddy 编程语言插图

和 Coddy 一起学编程

开始