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
来执行代码,可以看到类似下面的输出结果:
可以看到当传了一个 null
的时候,会抛出一个 ArgumentNullException
的异常,说明确实是做了 null 检查的
这个操作符不仅仅适用于方法参数,也可以用于委托参数、索引器等
What's inside
从上面的输出结果我们可以看到有做 null 检查,实际是什么样子的呢?我们可以反编译一下代码来看一下实际生成的代码是怎么样的
反编译的结果如下:
Program 类型和 Main 方法 是由编译器自动生成的,这是 C# 9 引入的顶级语句 (Top-Level Statements)
可以看到我们代码中的 Hello
方法没有了,有一个编译器生成的另外一个方法,它是我们原来方法的变形,只增加了一句代码
<PrivateImplementationDetails>.ThrowIfNull(name, "name");
我们再看一下其中的实现,实现如下:
可以看到在这里实现了 null 检查,如果参数是 null
就会抛出 ArgumentNullException
异常
看到这里相信大家都知道是怎么实现的了,那么有个问题可以思考一下,这里我们使用了一个方法,如果有两个这样的方法会是什么样的呢?<PrivateImplementationDetails>
这个类会生成两个吗?我们来尝试一下,我们把这个方法拷贝一下改个名字再来反编译一下
void Hello1(string name!!) => Console.WriteLine($"Hello, {name}!");
反编译结果如下:
可以看到实际是调用的同一个方法,<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/ 来体验编译器的新特性
.NET runtime 中的代码已经用上了这个新的操作符来简化参数的 null 检查,可以参考:https://github.com/dotnet/runtime/pull/64720
References
https://github.com/dotnet/csharplang/blob/c7361547c0c00e0116f6e4ac3767d7b6bc7442b6/proposals/param-nullchecking.md
https://github.com/dotnet/runtime/pull/64720
https://github.com/WeihanLi/SamplesInPractice/tree/master/CSharp11Sample