! (null 包容)运算符(C# 参考)
一元后缀 !
运算符是 null 包容运算符或 null 抑制运算符。 在已启用的可为空的注释上下文中,可以使用 null 包容运算符来声明可为空的引用类型的表达式 x
不为 null
:x!
。 一元前缀 !
运算符是逻辑非运算符。
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 属性告知编译器,当方法返回 true
时,IsValid
方法的参数不能是 null
:
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
不能是 if
语句中的 null
。 如需深入了解允许你提供有关变量 null 状态的其他信息的属性,请参阅使用属性升级 API 以定义 null 期望值。
C# 语言规范
有关详细信息,请参阅可为空的引用类型规范草案的 null 包容性运算符部分。