【问题标题】:In C# 8, how do I detect impossible null checks?在 C# 8 中,如何检测不可能的空值检查?
【发布时间】:2020-04-02 20:26:28
【问题描述】:

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

我正在迁移一个旧代码库,其中包含大量冗余或无法访问的代码,例如:

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

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

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

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

【问题讨论】:

  • 引用类型的可空性是编译时检查,但仍然可以从没有启用可空引用类型的代码中传递空值,因此仍然可以访问执行分支。
  • @madreflection 我知道从技术上讲它仍然可以访问,但是 99% 的时间我不在乎,如果有人将 null 作为非可空参数。我希望我的代码假设 NonNull 参数不为空。
  • 我不确定用于静态代码分析的 sonarlint 扩展是否已经实现了类似的规则,如果没有,将来可能会包含它(您也可以编写自己的验证规则)跨度>
  • 这样的空检查在公共 API 中绝对有用,因为您不知道调用者是否遵守可空性注释。但是在私有层中,在许多情况下移除它们是合理的(某些情况下可能仍然更喜欢深度防御)。这不是 C# 编译器旨在帮助解决的问题,但对于分析器来说似乎是一项完美的任务。
  • @JulienCouvreur 我完全同意。就我而言,代码是私有的,所以我同时拥有调用者和被调用者。由于我将所有警告都视为错误,并且被调用者也在进行可空性分析,因此我有理由确定我可以删除“== null”检查。如果被调用者错误地发送了 null,那是被调用者必须修复的编程错误:我不介意因此引发 NullReferenceException。

标签: c# null c#-8.0 nullable-reference-types


【解决方案1】:

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

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

然而,正如 the migration guide 中所述,任何不使用 C# 可空性上下文的外部代码都将完全忽略这一点:

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

鉴于此,通常认为最佳做法是继续在任何 publicprotected 成员中提供保护子句和其他可空性检查。

事实上,如果你使用微软的 Code Analysis 包——我会推荐它——它会警告你在这种情况下使用保护子句。他们考虑在 C# 8 的可空性上下文中删除此代码,但 decided to maintain it 由于上述问题。

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

void Blah(SomeClass a) {
  if (a == null) {
    throw new ArgumentNullException(nameof(a));
  }
  …
}

这比在源头抛出NullReferenceException 更可取,因为它向调用者传达他们可以通过显式命名传递的确切参数(在这种情况下)来避免这种情况作为null。这比仅仅获得NullReferenceException 以及可能对发生异常的内部代码的引用更有用。

至关重要的是,此异常并不是为了帮助调试你的代码——这就是代码分析正在为你做的事情。相反,它表明您已经确定了空值的潜在取消引用,并且您在源头解决了它。

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

void Blah(SomeClass a) {
  _ = a?? throw new ArgumentNullException(nameof(a));
}

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

简短的回答是你不能;此时,Roselyn 的静态流分析专注于识别解除引用空对象的可能性,而不是检测潜在的无关检查。

不过,如上所述,长答案是您不应该;在 Microsoft adds runtime validation 或强制为空上下文之前,这些空检查继续提供价值。

【讨论】:

  • 谢谢。不幸的是,这正是我所担心的。检查我要检查的内容没有警告(即使实现起来似乎微不足道)。看来这个功能的设计者并没有试图适应像我这样的开发者......所以,我将不得不手动搜索“== null”的所有情况以删除多余的过度防御代码。
  • @AdamWall:关于我的回答的某些事情一直困扰着我,经过思考后,我意识到我将来自 Microsoft 代码分析的警告与来自 Roslyn 的静态流分析的警告混为一谈? 结果,我原来的回复很可能是从你身边经过,并解释你很清楚的事情。我对此深表歉意。我已经更新了我的答案,以区分这两个警告——并直接回答你的问题。但我也保留了最初的指导,因为(我希望!)它将对其他开始使用不可空引用类型的读者有用。
  • 无意冒犯,我意识到我的问题可能有点简洁。我确实希望删除那些“== null”检查,因为我拥有调用者和被调用者,所以我可以依赖不发送空参数的人,因为我将所有这些警告视为错误。
  • 除此之外,对本主题感兴趣的读者也可能对a proposal to add runtime validation of parameters to C# 感兴趣。这最终可能会或可能不会伴随代码分析中的无关检查的检测。但它至少会大大减少确保空值检查所需的代码量,并使编译器满意地考虑空值。我还使用此提案的链接更新了答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多