C# 里的各种问号
2022 年 4 月 28 日 · 1 分钟读完
C# 里的问号有好几种意思,放在一起记。
| 写法 | 名字 | 意思 |
|---|---|---|
int? |
可空值类型 | 值类型也能为 null |
cond ? x : y |
三元运算符 | 条件为真取 x,否则取 y |
obj?.Method() |
空条件运算符 | obj 为 null 时不调用,结果为 null |
obj?[i] |
空条件索引 | 同上,用于数组和索引器 |
a ?? b |
空合并运算符 | a 为 null 时取 b |
a ??= b |
空合并赋值 | a 为 null 时把 b 赋给 a(C# 8) |
string? |
可空引用类型注解 | 告诉编译器这个引用可能为 null(C# 8) |
可空值类型:int?
值类型(int、double、DateTime……)有默认值,不能为 null。在类型后加 ? 就可以了:
int? num1 = null; // 可以为 null
Nullable<int> num2 = new Nullable<int>(); // 与上面完全等价
常用成员:num1.HasValue 判断有没有值,num1.Value 取值(为 null 时抛异常),num1.GetValueOrDefault() 为 null 时返回 0。读数据库里可能为空的列时经常用到。
三元运算符:cond ? x : y
string state = temperature > 80 ? "报警" : "正常";
空条件运算符:?.
class Device { }
Device s = null;
Console.WriteLine(s?.ToString()); // 不会抛 NullReferenceException,输出空行
对象不为 null 时才执行后面的调用,否则整个表达式的结果是 null。链式调用里任何一环为 null 都会短路:plc?.Connection?.Close();。
触发事件时也常这么写,线程安全又简洁:
DataReceived?.Invoke(this, e);
空合并运算符:?? 和 ??=
a ?? b:a 为 null 时返回 b,否则返回 a。
class Device
{
public override string ToString() => "s2";
}
Device s = null;
Device s2 = new Device();
s = s ?? s2;
Console.WriteLine(s?.ToString()); // s2

C# 8 起,上面那句可以写成 s ??= s2;。常见的懒加载写法:
private List<string> _names;
public List<string> Names => _names ??= new List<string>();
和 ?. 组合起来给默认值:
int count = list?.Count ?? 0;
string name = config?["name"] ?? "未命名";
延伸:可空引用类型(C# 8)
打开 <Nullable>enable</Nullable>(.NET 6 起的新项目默认打开)以后,string 表示「不会为 null」,string? 表示「可能为 null」。编译器会在你对 string? 直接点方法时给警告,把大量 NullReferenceException 提前到编译期发现。
- 确定不为 null 但编译器看不出来时,可以用
!告诉它:name!.Length,但不要滥用。 - 和可空值类型不同,
string?只是个注解,运行时就是普通的string,没有HasValue。 - C# 9 的模式匹配也能判空:
if (obj is not null) { ... },比!= null更不会被运算符重载干扰。
原文首发于 blog.csdn.net