TL;DR
理解 null!
的关键是理解 !
运算符。 你可能以前使用过它作为 "非" 运算符。然而,自从C# 8.0 及其新的 "可空引用类型" 特性出现后,这个运算符获得了第二个含义。它可以用于 类型 来控制 Nullability,因此被称为 "Null Forgiving Operator"。
基本上,null!
将 !
运算符应用于值 null
。这覆盖了值 null
的可为空性,告诉编译器 null
是一个 "非空" 类型。
典型用法
假设这个定义:
class Person
{
public string? MiddleName;
}
使用方法如下:
void LogPerson(Person person)
{
Console.WriteLine(person.MiddleName.Length);
Console.WriteLine(person.MiddleName!.Length);
}
这个操作符基本上是为了在这种情况下关闭编译器的空检查。
技术解释
你需要理解什么是 "null!" 的基础知识。
空安全性
C# 8.0试图帮助你管理你的空值。与其默认允许你将 null 赋给所有东西,他们反过来要求你明确地标记你想能够容纳 null 值的一切。
这是一个非常有用的功能,它可以通过强制你做出决策并执行它来避免 NullReferenceException。
工作原理
当谈论空安全性时,变量可以处于两种状态。
可为空 - 可以为 null。
不可为空 - 不能为 null。
自 C# 8.0 以来,所有引用类型默认都是不可为空的。自 C# 2.0 以来,值类型就已经是不可为空的了!
"可空性" 可以通过两个新的(类型级别的)操作符进行修改:
!
= 从Nullable
到Non-Nullable
?
= 从Non-Nullable
到Nullable
这些运算符是彼此的对应关系。
编译器使用您使用这些运算符定义的信息来确保空值安全。
示例
?
运算符的用法。
这个运算符告诉编译器一个变量可以保存一个空值。它在定义变量时使用。
可为空的 string? x;
x
是一个引用类型 - 所以默认情况下是非空的。
- 我们使用
?
运算符 - 使其可为空。
x = null
正常工作。
不可为空的 string y;
y
是一个引用类型 - 所以默认情况下是非空的。
y = null
会生成一个警告,因为你将一个空值赋给了一个不应该为空的变量。
有趣的是:使用 object?
实际上只是 System.Nullable<object>
的语法糖。
!
运算符的使用。
这个运算符告诉编译器,某个可能为空的东西是可以安全访问的。 你表达了在这种情况下“不关心”空安全的意图。 它用于访问变量。
string x;
string? y;
x = y
- 非法!
警告: "y" 可能为 null
- 赋值语句的左侧是非空的,但右侧是可空的。
- 因此,它是不正确的,从语义上讲是错误的。
x = y!
- 合法!
y
是一个引用类型,应用了 ?
类型修饰符,所以如果没有其他证明,它是可空的。
- 我们对
y
应用 !
,这样可以覆盖其空值设置,使其成为非空的
- 赋值语句的左右两侧都是非空的。从语义上讲是正确的。
警告: !
运算符只在类型系统级别上关闭编译器检查 - 在运行时,值仍然可能为 null。
请谨慎使用!
你应该尽量避免使用Null-Forgiving-Operator,因为使用它可能是系统设计缺陷的症状,会使编译器所保证的null安全性失效。
原因
使用"!"操作符会导致非常难以发现的错误。如果一个属性被标记为非空,你会认为可以安全地使用它。但在运行时,突然遇到NullReferenceException异常,让你感到困惑。因为通过"!"绕过了编译器的检查,实际上值变成了null。
那么为什么还存在这个操作符呢?
下面详细介绍了一些合理使用情况。然而,在99%的情况下,你最好选择其他解决方案。请不要在代码中滥用"!"来消除警告。
在某些(边缘)情况下,编译器无法检测到可为空的值实际上是不可为空的。
更容易迁移遗留代码库。
在某些情况下,你只是不关心某个东西是否变为null。
在进行单元测试时,您可能希望检查代码在出现null时的行为。
好吗?但是null!
是什么意思?
它告诉编译器null
不是一个可为空的值。听起来很奇怪,对吧?
这与上面的例子中的y!
是一样的。之所以看起来奇怪,是因为你将操作符应用于null
字面量。但概念是相同的。在这种情况下,null
字面量与任何其他表达式/类型/值/变量相同。
null
字面量类型是唯一默认可为空的类型!但正如我们所学到的,任何类型的可空性都可以通过!
覆盖为非可为空。
类型系统不关心变量的实际/运行时值,只关心其编译时类型,在你的例子中,你想要赋给LastName
(null!
)的变量是非可为空
的,就从类型系统的角度而言是有效的。
考虑一下这段(无效)代码。
object? null;
LastName = null!;
!
是空引用安全操作符,告诉编译器,即使它通常不允许,也应该睁一只眼闭一只眼,因为我们更清楚情况。null!
本身的实际用途很小,因为它几乎完全否定了可为空(nullable)引用类型的有用性。 当你知道表达式不可能是null
,但编译器不知道时,它就更有用了。 - Jeroen Mostertstring
不是可空引用类型,所以不应该为null
。赋值null!
实际上是在说:“我知道这不应该为 null,但猜猜怎么着,我还是这么做了。” 几乎没有任何程序需要这样做——唯一的原因是你知道你会在其他人能够得到NullReferenceException
之前分配一个非空值,并且想要表明你没有忘记分配它。这种情况可能出现,但不太可能,因此作为示例并不是很好。 - Jeroen Mostert