?? 返回左侧值(非null)或右侧默认值,??= 仅在左侧为null时赋值;二者均懒求值、不抛异常,且需左操作数为可空类型或引用类型。
?? 是空合并运算符:用默认值兜底 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 items = null;
items ??= new List(); // 第一次:赋值
items.Add("first");
int? value = null;
value ??= 42; // value 现在是 42
value ??= 99; // 不执行,value 仍是 42
?? 和 ??= 的关键区别在哪
核心不在“有没有等号”,而在于「是否修改左操作数」和「适用上下文」: