.net 为什么明显的NullReferenceException不是编译时错误?[已关闭]

vs91vp4v  于 2023-02-26  发布在  .NET
关注(0)|答案(2)|浏览(110)

昨天关门了。
截至22小时前,社区正在审查是否重新讨论这个问题。
Improve this question
我发现以前没有人问过这个问题是令人惊讶的,但是为什么编译器在这种明目张胆的情况下只限制自己发出警告呢?

object obj = null;
string str = obj.ToString(); //must be a compile-time error?

在反编译代码并看到以下内容后,我尤其无法理解:

((object) null).ToString();

我检查过类似的SO问题(12),答案归结为"不可能在编译时检查变量的值"。
但是为什么不可能呢?我错过了什么?这听起来不太正确,尤其是考虑到上面的反编译代码。

zour9fqk

zour9fqk1#

为什么它是一个 * 警告 * 而不是一个 * 错误 * 的简短答案是,空引用分析不是绝对正确的,并且不能容易地考虑到并行或间接引用。代码本身满足编译和执行的所有编译器规则。在运行时执行失败的事实不一定是一个错误,它只是一个异常,您的应用程序可能会处理也可能不会处理。

  • 开发人员可能特别希望引发运行时错误。

编译器不能假设开发者不知道这会导致运行时错误,一旦我们开始这样做,还有哪些假设可能出错?

错误表示真正的代码违规。变量值为null这一事实要求在运行时具体了解该变量的预期state。只有在执行obj.ToString()时,我们才能确定obj的值为空。在此之前的所有事情充其量只是一个假设。2编译器可能对约定和代码风格有意见的所有事情都变成了警告或注解。由每个开发人员或团队决定是将特定警告提升为错误状态,还是将所有警告都视为错误。

从另一个Angular 来考虑这个问题可能会有所帮助。你的例子代表了2行代码,它们碰巧在你的脚本中彼此相邻:

object obj = null;
string str = obj.ToString();

在 * 设计时 *,可以想象更多的代码 * 可能 * 存在于这两个语句之间,其净效应是附加逻辑 * 可能 * 将一个非空值设置为obj

object obj = null;
...
obj = "hello world!";
...
string str = obj.ToString();

但是并行处理呢?如果obj是从并行运行的另一个逻辑块设置的,比如在obj的示例化和obj.ToString()的调用之间,该值是在我们这里看到的代码之外设置的,那会怎么样呢?
...如果被调用的方法是一个扩展方法呢?毕竟扩展方法可以从空引用中调用。
.ToString()只是 * 明显 * 它会引发运行时错误,因为它是一个成员声明的方法,并且 * 如果 * 我们可以确定该对象尚未初始化。有很多可能性需要检查,并且在大型代码库中,需要为每个变量和每个调用花费此工作。这种类型的功能不在C#编译器的原始版本中。
20年前,即使我们可以,在标准硬件上对代码进行这一级别的分析也会显著降低开发人员的生产力。良好的SDLC实践,如在发布前测试代码,无论如何都会发现这些类型的问题,从编译器获得这一级别的检查根本不是优先事项。
快进到今天,编译器已经进化了,开发人员已经压制了所有的bug,并且能够专注于运行时和生产力的改进。这样的改进之一是Nullable Reference Type分析。这几乎涵盖了通过建立一套开发人员需要遵守的新约定来检查你的场景和真正复杂的场景。
如果你对将旧代码迁移到NRT感兴趣,请参考Update a codebase with nullable reference types to improve null diagnostic warnings,但要注意,许多现有的代码库在你第一次打开它时会有成千上万的警告。如果你可以忽略警告,那么我不知道你为什么要费心,对于我们这些 * 将警告视为错误 * 的人来说,你将有大量的代码需要审阅、修复或注解。
我将调用why I need to enable NUllable element in the csporj to use nullable reference types?的一个特定注解,Jeroen Mostert的这个注解是对 * 为什么 * 这不是一个 * 错误 * 的最佳总结,即使NRT意味着我们可以合理地假设这是一个错误:(* 重点是我 *)
至于它没有任何区别,那是设计使然。**NRT是经过专门和精心制作的,对现有代码的影响最小,除非您显式选择加入,以便确保该特性实际上可以增加价值,并逐渐与现有代码库集成,而不是一种要么全有要么全无的方法,这种方法不会被采用,因为没有人愿意在代码中预先修复200个警告,而这些代码以前运行良好。**这也是为什么,例如,在NRT未启用的情况下写入string?未被完全视为错误。
如果你在2019年错过了它,你应该阅读Embracing nullable reference types。我们中的许多人个人还没有完全接受这一点,还有更多的遗留代码库足够稳定或有足够健壮的测试,以至于为了利用NRT而重写代码库的价值较小或没有价值,事实上,这样做可能会对产品造成重大风险,因为我们应该在发布更新的代码之前重新测试所有已更改的内容。

vq8itlhq

vq8itlhq2#

激活nullable reference types并将所有甚至可为空的错误视为警告。
可以通过两种方式激活NRT:

  • 每个代码文件(或代码块)
  • 只需将#nullable enable放在代码文件第一行
  • 如果只需要某个特定的代码块,请使用#nullable enable启动该部分,并使用#nullable disable#nullable restore停止它。恢复接缝以恢复项目设置,但这在我们的.Net Framework 4.7.2项目中不起作用。在我们的示例中,disablerestore之间没有区别,但我们不再需要它,因为我们所有的项目现在都是NRT完整的。
  • 通过将<Nullable>enable</Nullable>放入项目的整个<PropertyGroup>中,这将激活整个项目的NRT,您不需要将其放入每个代码文件中。
  • 如果您使用一些代码生成器,如xsd.exe或类似的东西,您可以将#nullable disable放在代码文件的第一行,因为这些生成器不会创建NRT代码。
  • 您可以为每个构建配置设置这个选项,但是我看不出在一个配置中有NRT而在另一个配置中没有NRT有什么好处,因为由于新的类型注解,该配置将永远不会编译。

在此之后,如果编译器发现任何对空引用的访问,你都会得到警告。请记住,这不会找到所有可能的空引用用法,因为不是所有的代码都使用NRT注解,但它会找到很多。
要强制编译器产生错误而不是警告,请转到项目设置并强制警告为错误,或者如果您不希望所有警告都是错误,只需在 specific warnings 中键入nullable

相关问题