一元后缀 ! 运算符是 null 包容运算符或 null 抑制运算符。 在已启用的可为空的注释上下文中,使用 null 包容运算符来取消上述表达式的所有可为 null 警告。 一元前缀 ! 运算符是逻辑非运算符。 null 包容运算符在运行时不起作用。 它仅通过更改表达式的 null 状态来影响编译器的静态流分析。 在运行时,表达式 x! 的计算结果为基础表达式 x 的结果。
有关可为空引用类型特性的详细信息,请参见可为空引用类型。
示例
null 包容运算符的一个用例是测试参数验证逻辑。 例如,请考虑以下类:
#nullable enable
public class Person
{
public Person(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));
public string Name { get; }
}
使用 测试框架,可以在构造函数中为验证逻辑创建以下测试:
[TestMethod, ExpectedException(typeof(ArgumentNullException))]
public void NullNameShouldThrowTest()
{
var person = new Person(null!);
}
如果不使用 null 包容运算符,编译器将为前面的代码生成以下警告:Warning CS8625: Cannot convert null literal to non-nullable reference type。 通过使用 null 包容运算符,可以告知编译器传递 null 是预期行为,不应发出警告。
如果你明确知道某个表达式不能为 null,但编译器无法识别它,也可以使用 null 包容运算符。 在下面的示例中,如果 IsValid 方法返回 true,则其参数不是 null,可以放心取消对它的引用:
public static void Main()
{
Person? p = Find("John");
if (IsValid(p))
{
Console.WriteLine($"Found {p!.Name}");
}
}
public static bool IsValid(Person? person)
=> person is not null && person.Name is not null;
如果没有 null 包容运算符,编译器将为 p.Name 代码生成以下警告:Warning CS8602: Dereference of a possibly null reference。
如果可以修改 IsValid 方法,则可使用 NotNullWhen 属性告知编译器,当方法返回 IsValid 时,null 方法的参数不能是 true:
public static void Main()
{
Person? p = Find("John");
if (IsValid(p))
{
Console.WriteLine($"Found {p.Name}");
}
}
public static bool IsValid([NotNullWhen(true)] Person? person)
=> person is not null && person.Name is not null;
在前面的例子中,不需要使用 null 包容运算符,因为编译器有足够的信息来发现 p 不能是 null 语句中的 if。 如需深入了解允许你提供有关变量 null 状态的其他信息的属性,请参阅使用属性升级 API 以定义 null 期望值。
C# 语言规范
有关详细信息,请参阅可为空的引用类型规范草案的 null 包容性运算符部分。