何时在启用可空引用类型的情况下对参数进行空检查

Posted

技术标签:

【中文标题】何时在启用可空引用类型的情况下对参数进行空检查【英文标题】:When to null-check arguments with nullable reference types enabled 【发布时间】:2019-06-28 19:32:13 【问题描述】:

给定程序中使用 C# 8.0 的可空引用类型功能的函数,我是否仍应对参数执行空检查?

void Foo(string s, object o)

    if (s == null) throw new ArgumentNullException(nameof(s)); // Do I need these?
    if (o == null) throw new ArgumentNullException(nameof(o));
    ...

这些代码都不是公共 API 的一部分,所以我怀疑这些检查可能是多余的。这两个参数没有标记为可空,因此编译器应该警告任何调用代码可能传入空。

【问题讨论】:

如果您可以控制将调用此方法的整个代码库,并且所有代码库都是使用启用此功能的 C# 8 编译的,并且 你很勤奋并修复了编译器产生的所有警告/错误,那么你可以删除那些 if 语句。如果您不确定这些要求中的任何一个,那么您应该保留 if 语句。 通过现在已删除的答案中的 cmets,我很清楚,您并不是想了解编译器检查,而是询问其他人的观点。因此,我主要基于意见投票决定结束这个问题。正如答案和那些 cmets 中所述,编译器检查不能替代 if 语句或合同系统,并且很容易绕过。这种风险对您来说是否足够完全取决于您。但是,您似乎已经知道新的编译器检查的作用。 我使用 JetBrains Annotations 和 ReSharper/Rider 已经有一段时间了,它们可以帮助您确信您的代码正在做正确的事情,但它们不能替代实际检查所以我都有。在可空引用类型出现后,我将继续使用这两种方法,因为我编写的代码可能会被未经这些检查编译的代码所消耗,或者该代码的编写者甚至可能完全对编译器撒谎。 (众所周知,当我觉得需要时,我会到处使用奇怪的技巧)。 编译器无法对 any 调用代码发出真正的警告——存在会阻碍其分析的转义舱口和覆盖(如 null-forgiving 运算符)。您是否添加这些检查应该在很大程度上独立于您是否使用可为空的引用——如果您已经确信没有它们,您可以继续不使用它们;如果你没有,那么你可能应该把它们留在里面。新的检查降低了引入错误的可能性。它们是否使您不再需要它们不太可能将取决于。 @LasseVågsætherKarlsen 这是明确鼓励的基于意见的问题。这Many good questions generate some degree of opinion based on expert experience。在这个时间点,很多人都知道该功能的描述,但很少有人知道实际编译器限制。更少有迁移大型项目以使用可为空引用的经验。我可以说出 3 个名字,其中一个已经回答了。另外两人在 SO 【参考方案1】:

给定程序中使用 C# 8.0 的可空引用类型功能的函数,我是否仍应对参数执行空检查?

这取决于您对通过 API 的所有路径的确定程度。考虑这段代码:

public void Foo(string x)

    FooImpl(x);


private void FooImpl(string x)

    ...

这里FooImpl 不是公共API 的一部分,但如果Foo 不验证 参数,它仍然可以接收空引用。 (实际上,它可能依赖Foo 来执行参数验证。)

签入FooImpl 当然不是冗余,因为它在执行时执行编译器无法在编译时绝对确定的检查。可空引用类型提高了一般安全性,更重要的是提高了代码的表现力,但它们与 CLR 提供的类型安全性不同(阻止您将 string 引用视为 @例如 987654327@ 参考)。对于特定表达式在执行时是否为 null 的看法,编译器可能会以多种方式“错误”,并且无论如何都可以使用 ! 覆盖编译器。

更广泛地说:如果您的检查在C# 8 之前之前不是多余的,那么它们在C# 8 之后之后也不是多余的,因为可空引用类型功能不会改变为代码生成的 IL,而不是在属性方面。

因此,如果您的公共 API 正在执行所有适当的参数检查(上面示例中的 Foo),那么代码中的检查就已经是多余的了。你对此有多大信心?如果您绝对有信心并且错误的影响很小,那么可以肯定 - 摆脱验证。 C# 8 功能可能会帮助您对此充满信心,但您仍然需要注意不要过于自信 - 毕竟 - 上面的代码不会给出任何警告。

在为 C# 8 更新 Noda Time 时,我个人不会删除任何参数验证。

【讨论】:

not 让 C# 8.0 编译器为不可为空的引用类型方法参数生成 if (arg is null) throw IL 指令的原因是什么?如果编译器默认生成那个代码不是很好吗? @Steven:这会改变代码的行为,使启用该功能的风险更大。有一条坚定的路线“打开功能不会改变生成的 IL”,这使得推理变得非常容易,并且意味着您不会仅仅因为您的期望错误而破坏您的代码无害”的方式。我希望有语法可以让你表达“这个参数是非空的,请帮我检查一下”,但我支持默认不这样做的决定。 谢谢乔恩,这是有道理的。不过,我不确定您正在谈论的其他语法。我宁愿看到启用此功能的编译器开关/项目设置(最好默认为新创建的 C# 8 项目启用)。 据我了解,C# 8 引用类型不能为空。我仍然不明白为什么如果变量不能为 null,我需要检查 null 吗? @Youssef13:运行时不会阻止引用为空。你只会收到警告。因此,变量“不能为空”或“变量不能为空”的情况并非如此。如果您的代码是唯一可以为某个路径提供值的代码,并且您绝对确定您永远不会传入 null (或者该值是派生的),那么可以确定,删除检查 - 但如果该值可以来自在其他地方(例如,提供参数的调用者)那么您应该检查,因为它可能为空。

以上是关于何时在启用可空引用类型的情况下对参数进行空检查的主要内容,如果未能解决你的问题,请参考以下文章

可空类型产生警告,因为项目已启用可空引用类型

将可空引用类型转换为不可空引用类型,不那么冗长

可空引用类型 - 通过接受的参数返回类型可空性

使用可空引用类型时,OData 元数据不生成可空方面

未从 FirstOrDefault 公开的可空引用类型信息

引用类型与可空类型 ToString()