C# 11中的参数null检查

Intro

C# 11 将引入一个新的操作符 !! 来简化我们代码中的对于参数的 null 检查,昨天发布的 .NET 7 Preview 1 已经支持了这一语法,感兴趣的不妨来试一下吧,下面我们就来看一下如何使用吧。.

Prepare

如果你想在本地代码中进行编译测试,需要安装 .NET 7 Preview 1 的 SDK,下载地址:

然后在本地创建一个控制台应用程序,可以通过命令 dotnet new console 来创建

创建成功之后,手动修改项目文件,配置 C# 语言版本为 preview,如下所示添加 <LangVersion>preview</LangVersion>

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>    
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
+   <LangVersion>preview</LangVersion>
  </PropertyGroup>

</Project>

Sample

!! 是一个新的操作符,加在参数后面编译器会自动生成一段 null 检查的代码

bang-bang operator

下面我们就来试一下吧,测试代码如下:

Hello("World");

try
{
    Hello(null!);
}
catch (Exception ex)
{
    Console.WriteLine(ex);
}

void Hello(string name!!)
{
    Console.WriteLine($"Hello, {name}!");
}

运行 dotnet run 来执行代码,可以看到类似下面的输出结果:

C# 11中的参数null检查

可以看到当传了一个 null 的时候,会抛出一个 ArgumentNullException 的异常,说明确实是做了 null 检查的

这个操作符不仅仅适用于方法参数,也可以用于委托参数、索引器等

What's inside

从上面的输出结果我们可以看到有做 null 检查,实际是什么样子的呢?我们可以反编译一下代码来看一下实际生成的代码是怎么样的

反编译的结果如下:

C# 11中的参数null检查

Program 类型和 Main 方法 是由编译器自动生成的,这是 C# 9 引入的顶级语句 (Top-Level Statements)

可以看到我们代码中的 Hello 方法没有了,有一个编译器生成的另外一个方法,它是我们原来方法的变形,只增加了一句代码

<PrivateImplementationDetails>.ThrowIfNull(name, "name");

我们再看一下其中的实现,实现如下:

C# 11中的参数null检查

可以看到在这里实现了 null 检查,如果参数是 null 就会抛出 ArgumentNullException 异常

看到这里相信大家都知道是怎么实现的了,那么有个问题可以思考一下,这里我们使用了一个方法,如果有两个这样的方法会是什么样的呢?<PrivateImplementationDetails> 这个类会生成两个吗?我们来尝试一下,我们把这个方法拷贝一下改个名字再来反编译一下

void Hello1(string name!!) => Console.WriteLine($"Hello, {name}!");

反编译结果如下:

C# 11中的参数null检查

可以看到实际是调用的同一个方法,<PrivateImplementationDetails> 这个类型只生成了一次

那如果这两个方法是在两个项目中会怎么样呢?可以自己动手试一下~~

More

这个操作符使用时,还有一些注意事项

如果你启用了可空引用类型,并将参数声明为可空的引用类型,编译器会产生一个警告,因为实际上是不应该为 null 的,为 null 就会抛异常,所以编译器会警告,示例如下:

// warning CS8995: Nullable type 'string?' is null-checked and will throw if null.
// void Hello2(string? name!!) => Console.WriteLine($"Hello, {name}!");

值类型是不能使用这个操作符的,因为值类型是不会为 null 的,编译器会直接报错,但可空值类型是可以的,例如:

// error CS8992: Parameter 'int' is a non-nullable value type and cannot be null-checked.
// void Hello3(int name!!) => Console.WriteLine($"Hello, {name}!");

另外 out 参数也不能使用这个操作符,如:

// error CS8994: 'out' parameter 'name' cannot be null-checked.
// void Hello4(out string name!!) => name = "World";

想要尝试的小伙伴可以装一下 .NET 7 preview 1 来体验,如果不想装 preview 也可以通过 一个在线网站 sharplab https://sharplab.io/ 来体验编译器的新特性

C# 11中的参数null检查

.NET runtime 中的代码已经用上了这个新的操作符来简化参数的 null 检查,可以参考:https://github.com/dotnet/runtime/pull/64720