在HNQ上阅读this question之后,我继续阅读Nullable Reference Types in C# 8并进行了一些实验。
我非常清楚,当有人说“我发现了编译器错误!”时,十分之九,甚至更多。这实际上是设计使然,并对自己产生误解。而且因为我直到今天才开始研究此功能,所以显然我对它没有很好的了解。顺便说一句,让我们看下面的代码:
#nullable enable
class Program
{
static void Main()
{
var s = "";
var b = s == null; // If you comment this line out, the warning on the line below disappears
var i = s.Length; // warning CS8602: Dereference of a possibly null reference
}
}
阅读我上面链接的文档后,我希望
s == null
行会向我发出警告-毕竟
s
显然是不可为空的,因此将其与
null
进行比较是没有意义的。
相反,我在下一行收到一条警告,警告说
s
可能是空引用,即使对于人类来说,显然不是。
而且,如果我们不将
s
与
null
进行比较,则会显示警告
而不显示。
我做了一次Google搜索,然后点击了 a GitHub issue,结果完全是另外一回事。但是在此过程中,我与一名贡献者进行了交谈,从而对该行为提供了更多见解(例如,“空检查通常是一种有用的判断方法编译器重置有关变量可为空性的先前推断。”)。但是,这仍然使我没有回答主要问题。
与其创建一个新的GitHub问题,而不是花大量时间来忙于项目贡献者,我将其发布给社区。
您能否解释一下发生了什么,为什么?特别是,为什么在
s == null
行上没有生成警告,为什么在这里似乎没有
CS8602
引用的情况下却显示
null
?如果可空性推断不是防弹的,如链接的GitHub线程所暗示的,它怎么会出错?那有什么例子呢?
请您参考如下方法:
这实际上是@stuartd链接的答案的重复,因此在这里我不再赘述。但是,问题的根源在于这既不是语言错误也不是编译器错误,而是它的预期行为与实现的完全相同。我们跟踪变量的空状态。最初声明变量时,该状态为NotNull,因为您使用不为null的值显式初始化了该状态。但是我们没有追踪到NotNull的来源。例如,这实际上是等效的代码:
#nullable enable
class Program
{
static void Main()
{
M("");
}
static void M(string s)
{
var b = s == null;
var i = s.Length; // warning CS8602: Dereference of a possibly null reference
}
}
在这两种情况下,您都需要针对
s
显式测试
null
。正如Mads在这个问题中回答的那样,我们将其作为流分析的输入:
https://stackoverflow.com/a/59328672/2672518。在该答案中,结果是您收到退货警告。在这种情况下,答案是您收到警告,说您取消引用了可能为空的引用。
It does not become nullable, simply because we were silly enough to compare it with
null
.
是的,实际上是这样。给编译器。作为人类,我们可以看一下这段代码,显然可以理解,它不能引发空引用异常。但是在编译器中实现可为空的流分析的方法却不能。我们确实讨论了此分析的一些改进,其中我们根据值的来源添加了其他状态,但我们认为这样做会给实现增加很多复杂性,而不会带来很多好处,因为唯一的地方这对于这样的情况很有用,在这种情况下,用户使用
new
或常数值初始化变量,然后无论如何都要检查它的
null
。