← 全部文章 C# 语言

C# 里的各种问号

2022 年 4 月 28 日 · 1 分钟读完

目录
  1. 可空值类型:int?
  2. 三元运算符:cond ? x : y
  3. 空条件运算符:?.
  4. 空合并运算符:?? 和 ??=
  5. 延伸:可空引用类型(C# 8)

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 更不会被运算符重载干扰。

# C# # 可空类型 # 语法糖

原文首发于 blog.csdn.net