这是一个将在 C# 11 中引入的空参数检查语法。此提案有 since been rolled back 遵循社区反馈。
提案 is here,以及 PR 首次向运行时推出 is here。
语法:
public void Foo(string bar!!)
{
}
大致相当于:
public void Foo(string bar)
{
if (bar is null)
{
throw new ArgumentNullException(nameof(bar));
}
}
...虽然实际的实现使用了一个 throw 助手,比如:
public void Foo(string bar)
{
<PrivateImplementationDetails>.ThrowIfNull(bar, "bar");
}
[CompilerGenerated]
internal sealed class <PrivateImplementationDetails>
{
internal static void Throw(string paramName)
{
throw new ArgumentNullException(paramName);
}
internal static void ThrowIfNull(object argument, string paramName)
{
if (argument == null)
{
Throw(paramName);
}
}
}
See on SharpLab.
包含throw 语句的方法不太可能被 JIT 内联,因此使用 throw 帮助器更有可能内联您的方法,这可能会完全消除空检查! See on SharpLab.
请注意,!! 的使用是您方法的实现细节:它只会导致编译器插入您本来可以自己编写的代码。这意味着从 throw 移动到 !!(反之亦然)并不是一个重大变化。
有几个地方!! 会让编译器生成您不能(轻松)手动编写的代码。
!! 特别有用的一个地方是在具有主构造函数的记录中。例如:
public record Person(string Name!!, int Age);
为了在以前的 C# 版本中正确地对 Name 参数进行空值检查,您必须将其写出来:
public record Person
{
public string Name { get; init; }
public int Age { get; init; }
public Person(string name, int age)
{
if (name is null)
throw new ArgumentNullException(nameof(name));
(Name, Age) = (name, age);
}
public void Deconstruct(out string name, out int age) =>
(name, age) = (Name, Age);
}
!! 做一些你自己不能写的事情的另一个地方是链式构造函数调用:
public class C
{
public C(int i) { }
public C(string s!!) : this(s.Length) { }
}
在访问 s.Length 之前,这个空值检查 s,类似这样(这不是有效的 C#):
public C(string s)
{
if (s is null)
throw new ArgumentNullException(nameof(s));
C(s.Length);
}
See on SharpLab.
另一个有趣的方面是在构造函数中的字段分配之前插入空检查。例如:
public class C
{
private readonly ExpensiveObject e = new ExpensiveObject();
public C(string s!!) { }
}
编译为:
public class C
{
private readonly ExpensiveObject e;
public C(string s)
{
if (s is null)
throw new ArgumentNullException(nameof(s));
e = new ExpensiveObject();
}
}
也就是说,空值检查发生在ExpensiveObject 的实例化之前。 See on SharpLab.