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

Why isn't obvious NullReferenceException a compile-time error? [closed]

提问人:Razor23 Donetsk 提问时间:2/23/2023 最后编辑:Razor23 Donetsk 更新时间:3/7/2023 访问量:173

问:


想改进这个问题吗?通过编辑这篇文章添加详细信息并澄清问题。

9个月前关闭。

令我感到惊讶的是,以前没有人问过这个问题,但为什么编译器在如此明目张胆的情况下将自己限制在警告上?

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

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

((object) null).ToString();

我检查了相似(但不同)的 SO 问题(12),答案归结为“无法在编译时检查变量的值”。
但为什么这是不可能的呢?我在这里错过了什么?编译器能看到它产生的结果吗?

C# .NET nullreferenceexception 编译器警告

评论

3赞 MakePeaceGreatAgain 2/23/2023
您是否希望编译器在编译后反编译程序集以检查这一点?
2赞 Sebastian Schumann 2/23/2023
是否激活了可为 null 的引用类型?如果是这样,编译器将警告您。如果将所有警告视为错误,则不会编译。DEMO - 但只有一个警告,因为我无法更改该在线编译器的任何编译时设置。我们之所以使用该功能,是因为它可用,并且即使在 .Net Framework 项目中也能正常工作。
0赞 Razor23 Donetsk 2/24/2023
@MakePeaceGreatAgain,我不明白你的问题。编译器在编译过程中不能检查吗?即使在查看了“正常”代码之后,我也希望看到错误,我只是说反编译版本让我更加想知道。
0赞 Razor23 Donetsk 2/25/2023
@TylerH,真的是这样吗?我想我问的是一个非常具体和详细的部分,关于这些问题中没有解决的问题。证明我错了。我从克里斯·沙勒(Chris Schaller)那里得到了答案,但不是在那里。
0赞 TylerH 2/25/2023
@Razor23Donetsk 不知道“证明我错了”是什么意思;你只需要阅读答案就可以被证明是错误的。

答:

1赞 Chris Schaller 2/24/2023 #1

为什么它是警告而不是错误,简短的回答是,空引用分析不是万无一失的,不能轻易考虑并行或间接引用。代码本身满足编译和执行的所有编译器规则。在运行时执行失败的事实不一定是错误,它只是应用程序可能处理也可能不处理的异常。

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

编译器不能假定开发人员不知道这将导致运行时错误。一旦我们开始这样做,我们还可能犯错哪些其他假设?

错误是针对真正的代码冲突。变量的值 这一事实需要对该变量在运行时的预期有特定的了解。只有在执行时,我们才能确定 的值为 null,到目前为止的所有其他内容充其量只是一个假设。编译器可能有意见的所有其他内容(如约定和代码样式)都将成为警告或注释。由每个开发人员或团队决定是否要将特定警告提升为错误状态,或者是否将所有警告视为错误。nullstateobj.ToString()obj


从另一个角度思考这个问题可能会有所帮助。您的示例表示 2 行代码,它们恰好在今天的脚本中彼此相邻存在:

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

设计时,可以想象这两个语句之间可能会存在更多的代码,这样做的最终效果是,额外的逻辑可能会将非 null 值设置为:obj

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

但是并行处理呢,如果从并行运行的不同逻辑块设置,这样在实例化和对值的调用之间设置在我们在此处看到的代码之外呢?objobjobj.ToString()

...如果调用的方法是扩展方法,该怎么办?毕竟,扩展方法可以从 null 引用调用。

.ToString()很明显,它会引发运行时错误,因为它是一个成员声明的方法,并且我们可以确定该对象尚未初始化。有很多可能性需要检查,在大型代码库中,需要为每个变量和每个调用花费这种努力。这种类型的功能在 C# 编译器的原始版本中没有。

20 年前,即使我们可以,在标准硬件上对代码进行这种级别分析也会大大降低开发人员的工作效率。良好的 SDLC 实践(例如在发布之前测试代码)无论如何都会发现这些类型的问题,从编译器那里获得这种级别的检查根本不是优先事项。

快进到今天,编译器已经发展,开发人员已经消除了所有错误,并能够专注于运行时和生产力的改进。其中一项改进是可为空的引用类型分析。这几乎涵盖了通过建立一组开发人员需要遵守的新约定来检查您的场景和非常复杂的场景。

如果您有兴趣将旧代码迁移到 NRT,请参阅使用可为 null 的引用类型更新代码库以改进 null 诊断警告,但请注意,许多现有代码库在首次启用此功能时将有数千个警告。如果你可以忽略警告,那么我不确定你为什么要打扰,对于我们其他人来说,将警告视为错误,你将有很多代码需要审查、修复或注释。

我将指出为什么我需要在 csporj 中启用 NUllable 元素以使用可为 null 的引用类型?Jeroen Mostert 的这条评论是对为什么这不是错误的最佳总结,即使 NRT 意味着我们可以合理地假设这是一个错误:(强调是我的)

至于它没有任何区别,那是设计使然。NRT 经过专门和精心设计,对现有代码的影响最小,除非您明确选择加入,以确保该功能可以实际增加价值并逐渐与现有代码库集成,而不是采用全有或全无的方法,因为没有人愿意在以前工作良好的代码中预先修复 200 个警告。这也是为什么,例如,写字符串?未启用 NRT 的地方并不完全被视为错误。

如果您在 2019 年错过了它,您应该阅读拥抱可为 null 的引用类型。我们中的许多人还没有完全接受这一点,还有更多的遗留代码库足够稳定或具有足够强大的测试,因此重写代码库以利用 NRT 的价值较小或没有价值,事实上,这样做可能会对产品造成重大风险,因为我们应该在发布更新的代码之前重新测试所有已更改的内容。

评论

1赞 Chris Schaller 2/24/2023
我找不到具体的来源来引用,但在讨论这个问题时,一个常见的回应是“开发人员并没有那么愚蠢,如果他们是,那么在运行时引发的 NullReferenceException 将绰绰有余,特别是考虑到前面的一行将解释错误。 注意:启用 NRT 后,警告将对开发人员来说显而易见。由于代码在代码文件中具有接触式接近性,因此为每个检测到的 NRE 做一个特殊情况的努力是不值得的。检查NRT警告,这不会有问题。
1赞 Chris Schaller 2/24/2023
2017 年 Mads Torgersen 的有趣侧面阅读:devblogs.microsoft.com/dotnet/......
1赞 Chris Schaller 2/24/2023
这个查询尽可能接近:github.com/dotnet/csharplang/discussions/categories/...如果你想在语言创意的讨论论坛上发表你的想法,那么这里就是这个地方,但要准备好详细解释控制你的预期场景的逻辑规则。
1赞 Chris Schaller 2/26/2023
int i = 10 / 0 is different, because literals are used the expression is immutable and can be evaluated at compile time. NRTs are interpreted and often open to interpretation, as such there is a degree of uncertainty, this I tried to explain in my post is the most important reason that they cannot be errors by default. If I had to choose your feature over all the others I want to see implemented in C#, identifying this one as an error goes to the bottom of the list for me, it is already a warning, so the compiler is already doing this check for you, can't ask for much else.
1赞 Chris Schaller 2/27/2023
So short answer, that is not what the compiler sees. The compiler still see's a call to the instance method on a variable. When you decompile the IL back to C#, the decompiler further transposes the variable declaration to be inline with the method call to avoid creating an interim variable. You need to look at the IL to view the actual output. Could the compiler do this, sure, but only by parsing the output which would be a significant overhead to detect a trivial case that is already flagged in the general CS8600 nullable warning.[System.Runtime]System.Object::ToString()