c# ?? 和 ??= 运算符是什么意思

来源:这里教程网 时间:2026-02-21 17:41:09 作者:

?? 是空合并运算符:用默认值兜底 null

当左侧表达式为

null
(或可空类型的
null
)时,
??
直接返回右侧值;否则就原样返回左侧值。它不抛异常、不执行右侧表达式(懒求值),适合快速设默认值。

常见错误:写成
a = b ?? "default"
却没意识到
b
是非可空类型(如
int
),编译直接报错——
??
要求左操作数必须是可空类型(
T?
)或引用类型
典型场景:配置读取、API 返回值处理、UI 字段 fallback 性能注意:右侧表达式只在左侧为
null
时才执行,比如
GetDefaultName() ?? "Guest"
中,
GetDefaultName()
不会无故调用
string name = user?.Name ?? "Anonymous";
int? count = data?.Length ?? 0; // data 可能为 null,Length 是 int?,所以合法
// ❌ 错误示例(编译失败):
// int i = 5 ?? 10; // int 不可为 null,不能用 ??

??= 是空合并赋值运算符:只在 null 时才赋值

??=
是 C# 8.0+ 引入的语法糖,作用等价于 “如果左边是
null
,就把右边值赋给它”,且同样跳过右侧计算(懒赋值)。它不是简单缩写
a = a ?? b
,而是语义更精确的“仅当需要时才初始化”。

必须满足:左操作数是变量、属性或索引器访问(即有“存储位置”),不能是常量或方法调用结果 典型场景:延迟初始化缓存、集合首次填充、避免重复 new 对象 容易踩坑:连续链式使用时,右侧表达式可能被多次求值(如果没封装好),例如
cache ??= GetNewCache()
每次都调用
GetNewCache()
—— 但只要
cache
非 null,就不会再进
List<string> items = null;
items ??= new List<string>(); // 第一次:赋值
items.Add("first");
<p>int? value = null;
value ??= 42; // value 现在是 42
value ??= 99; // 不执行,value 仍是 42

?? 和 ??= 的关键区别在哪

核心不在“有没有等号”,而在于「是否修改左操作数」和「适用上下文」:

??
是纯表达式,返回一个值,不改变任何变量(类似
a + b
??=
是赋值语句,必须左操作数可写(
var
声明的局部变量、字段、
ref
参数、属性 set 访问器内等)
两者都支持泛型约束:可与
T?
或引用类型泛型参数一起用,但不能用于不可空的
struct
(除非显式转为可空)
结合使用很自然:
cache ??= new Dictionary<string string>(); cache["key"] = value ?? "default";</string>

替代方案对比:为什么推荐 ?? / ??= 而不是 if 或 ?:?

它们不是功能上不可替代,而是更安全、更简洁、更符合现代 C# 的空安全设计意图:

if (x == null) x = y;
多行、易漏锁、无法嵌入表达式中(比如 LINQ 查询里)
x != null ? x : y
冗余判断(
x
被算两次)、对引用类型不安全(竞态下可能中途变 null)
GetValueOrDefault()
仅适用于可空值类型,且不能指定任意默认值(只能用默认构造)
??
??=
编译后生成高效 IL,没有运行时反射或装箱开销,且被 Roslyn 和 IDE 全面支持(智能提示、null 分析警告)

真正容易被忽略的是:这两个运算符和

?. 
(空条件调用)配合使用时,能构建出极简又健壮的空安全链式表达式,比如
config?.Features?.TimeoutMs ?? 30000
—— 这种写法一旦习惯,就很难退回手写 null 检查了。

相关推荐