! (null 包容)运算符(C# 参考)

一元后缀 ! 运算符是 null 包容运算符或 null 抑制运算符。 在已启用的可为空的注释上下文中,可以使用 null 包容运算符来声明可为空的引用类型的表达式 x 不为 nullx!。 一元前缀 ! 运算符是逻辑非运算符

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 包容性运算符部分。

请参阅