可空类型(Nullable)
T? 表示「有值或没有」。非可空类型编译期保证非 null,判空只在可空类型上有意义。基础写法(??、?.、if val)见运算符与条件分支,本页则重点介绍类型层面的规则。
解包四手段
可空值要拿回 T,只有四条路:?? 给默认、! 断言、?. 链式短路、判空流收窄:
func FindUser(id: int) -> string?
{
if id == 1
{
return "甲";
}
return null;
}
val name = FindUser(1);
print(name ?? "无名");
print(FindUser(2) ?? "无名");甲
无名! 断言宣告「此处必有值」,运行期为 null 时报 MS6045。?? 可以连起来给多级兜底:
val merged = name ?? FindUser(2) ?? "双无";
print(merged);
var nick: string? = "在手";
print(nick!.Length);
print(nick == null);甲
2
False判空流收窄
== null 判断不只是产生 bool,它产生「null 状态事实」,沿控制流传播。守护式写法(判空分支以 return / fail / crash 结束)之后,编译器把值当非空对待:
func GuardStyle(id: int) -> string
{
val name = FindUser(id);
if name == null
{
return "匿名";
}
return $"找到 {name.Length} 字";
}
print(GuardStyle(1), GuardStyle(2));找到 1 字 匿名守护之后的 name.Length 合法,不需要 ?. 或 !。正向式 if name != null { } 的块内同样收窄。&& 左侧的收窄在右侧可见(name != null && name.Length > 0)。
算术传播
可空值参与算术,结果也是可空:任一侧为 null,整个表达式结果就为 null:
val a: int? = 2;
val b: int? = null;
print(a + 1, b ?? -1);
print(a + b);3 -1
null比较的结果恒是 bool(null 参与 == / != 判断合法),但排序比较(< <= > >=)不接受任何可空操作数,null 没有次序,先 ?? 给默认值再比。
收窄的传播与失效
收窄沿控制流传播,也在特定操作后失效。常用形态的完整清单:
&&链:左侧建立的收窄在右侧可见(name != null && name.Length > 0整条合法)。- 三元真臂:
name != null ? name.Length : 0的真臂内收窄。 - 字段路径:
user.nick != null之后user.nick.Length直接可用。 - 重赋值失效:
o = expr之后按 expr 的类型重算状态。 - 调用毒化:判空之后、读之前若发生任何函数调用,字段路径的收窄全部失效,裸名局部不受影响。
var s: string? = "值";
if s != null && s.Length > 0
{
print("长名", s.Length);
}
val t = s != null ? s.Length : 0;
print(t);长名 1
1空合并赋值与空条件索引
??= 只在左侧为 null 时赋值;?[i] 把空条件推广到索引:
var who: string? = null;
who ??= "默认名";
print(who);
var arr: int[]? = [1, 2, 3];
print(arr?[1] ?? -1);
arr = null;
print(arr?[1] ?? -1);默认名
2
-1装箱标量的保守面
当前装箱标量的收窄比较保守,装箱标量(int? / float? / bool? / decimal?)判空后不收窄:while n != null 体内,n 仍是 int?,序数比较照样被拒,先 ?? 落地再算:
var n: int? = 3;
while n != null
{
val cur = n ?? 0;
print("轮", cur);
if cur <= 1
{
n = null;
}
else
{
n = cur - 1;
}
}轮 3
轮 2
轮 1- 被 lambda 或 spawn 捕获的变量、泛型参数、向量与元组叶段同样不收窄。
- 字符串与自定义类的引用可空不受此限,判空收窄全量生效。
- 可空元组本身照常收窄:判空分支内可以直接解构,见元组。
注意
- 赋 null 给非空类型的任何位置都报错。
- 非可空值与 null 的比较也会直接报错,判空只对可空形有意义;
- 收窄的事实源只有三个:
== null/!= null判断与is T测试,重新赋值按新值的类型重算。 - 判空收窄后多余的
??会报 MS3240 警告(右分支永不执行)。 - 排序比较不接受任何可空操作数(null 侧无序),先
??再比。
可空函数类型
函数类型的 ? 缀在返回类型上:func(int) -> int? 是「返回 int? 的函数」。
要「可能为 null 的函数类型」,须给整个函数类型加括号写 (func(int) -> int)?,可空函数调用前照常判空收窄:
func Maybe(n: int) -> int?
{
if n > 0 { return n; }
return null;
}
func On(n: int) -> int
{
return n;
}
func main() -> int
{
val f1: func(int) -> int? = Maybe;
print(f1(5) == 5);
val f2: (func(int) -> int)? = null;
print(f2 == null);
val f3: (func(int) -> int)? = On;
if (f3 != null)
{
print(f3(7));
}
return 0;
}True
True
7