在 C# 8 中,如何检测不可能的空检查?

Ada*_*all 6 c# null c#-8.0 nullable-reference-types

我已经开始在 C# 8 中使用可为 null 的引用类型。到目前为止,除了一件小事之外,我很喜欢这种改进。

我正在迁移一个旧的代码库,它充满了很多冗余或无法访问的代码,例如:

void Blah(SomeClass a) {
  if (a == null) {
    // this should be unreachable, since a is not nullable
  }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,我没有看到任何可以为我标记此代码的警告设置!这是微软的疏忽,还是我遗漏了什么?

我也使用 ReSharper,但它的警告设置似乎也没有捕捉到这一点。有没有其他人找到解决这个问题的方法?

编辑:我知道从技术上讲这仍然可以访问,因为可空性检查不是防弹的。这不是重点。在这种情况下,我将参数声明为NOT nullable,检查它是否为通常是一个错误。在 null 作为不可空类型传入的罕见事件中,我更愿意查看NullReferenceException并追踪错误传入 null 的违规代码。

Jer*_*ney 6

值得注意的是,可空性检查不仅不是防弹的,而且虽然它们旨在阻止调用者发送null引用,但它们没有采取任何措施来阻止它。代码仍然可以编译将 anull发送到此方法,并且没有对参数值本身进行任何运行时验证。

如果您确定所有调用者都将使用 C# 8 的可空性上下文——例如,这是一种internal方法——并且真的很努力地解决了 Roslyn 静态流分析中的所有警告(例如,您已经配置了构建服务器以处理它们作为错误)那么你是正确的,这些空检查是多余的。

但是,如迁移指南所述,任何不使用 C# 可空性上下文的外部代码都将完全忽略这一点:

新语法不提供运行时检查。外部代码可能会绕过编译器的流程分析。

鉴于此,通常认为最佳实践是继续在 anypublic或member 中提供保护子句和其他可空性检查protected

事实上,如果您使用 Microsoft 的代码分析包(我推荐它),它会警告您在这种情况下使用保护子句。他们考虑为 C# 8 的可空性上下文中的代码删除它,但由于上述问题,他们决定保留它

当您从代码分析中收到这些警告时,您可以将您的代码包装在空检查中,就像您在此处所做的那样。但是你也可以抛出异常。事实上,你可以扔另一个——NullReferenceException尽管这绝对不推荐。在这种情况下,您应该抛出一个ArgumentNullException, 并将参数的名称传递给构造函数:

void Blah(SomeClass a) {
  if (a == null) {
    throw new ArgumentNullException(nameof(a));
  }
  …
}
Run Code Online (Sandbox Code Playgroud)

这比NullReferenceException在源上抛出 a 更可取,因为它通过显式命名作为 a 传递的确切参数(在本例中)与调用者通信,他们可以做什么来避免这种情况null。这比仅获取异常发生的位置(NullReferenceException并且可能是对您的内部代码的引用)更有用。

重要的是,此异常并不意味着帮助调试你的代码-这就是代码分析为你做的。相反,它表明您已经确定了对空值的潜在取消引用,并且您已经在源代码中考虑了它。

注意:这些保护子句可能会给您的代码添加很多混乱。我的偏好是创建一个可重用的内部实用程序,通过一行来处理这个问题。或者,上述代码的单行简写是:

void Blah(SomeClass a) {
  _ = a?? throw new ArgumentNullException(nameof(a));
}
Run Code Online (Sandbox Code Playgroud)

这是回答您的原始问题的一种非常迂回的方式,即如何检测 C# 的不可空引用类型所不需要的空检查的存在。

简短的回答是你不能;在这一点上,Roslyn 的静态流分析侧重于识别取消引用空对象的可能性,而不是检测潜在的无关检查。

但是,如上所述,长期的答案是您不应该;在 Microsoft添加运行时验证或强制要求可空性上下文之前,这些空值检查会继续提供价值。