C#中的空参数检查
在C#中,是否有任何很好的理由(除了一个更好的错误消息)添加参数空检查每个函数的空值不是一个有效的值? 显然,使用s的代码无论如何都会抛出exception。 而这样的检查使代码变得越来越难以维护。
void f(SomeType s) { if (s == null) { throw new ArgumentNullException("s cannot be null."); } // Use s }
是的,有很好的理由:
- 它确切地标识了什么是null,从
NullReferenceException
可能不明显 - 即使其他条件意味着该值未被解除引用,也会使代码在无效input上失败
- 在这个方法可能会有第一个解除引用之前可能会遇到的任何其他副作用之前 ,会发生exception
- 这意味着你可以确信,如果你将parameter passing给别的东西,那么你并没有违反他们的合同
- 它logging了你方法的要求(当然使用代码合同更好)
至于你的反对意见:
- 速度较慢 :您是否发现这实际上是代码中的瓶颈,或者您是否猜测? 无效检查非常迅速,在绝大多数情况下,它们不会成为瓶颈
- 这使得代码难以维护 :我认为是相反的。 我认为使用代码更容易 ,不pipe参数是否为空,以及您确信该条件被强制执行的地方都很清楚。
而对于你的断言:
显然,使用s的代码无论如何都会抛出exception。
真? 考虑:
void f(SomeType s) { // Use s Console.WriteLine("I've got a message of {0}", s); }
这使用s
,但它不会引发exception。 如果s
为空无效,表示出现了问题,那么这里最适合的例外是exception。
现在,你把这些论证validation检查是一个不同的问题。 你可能会决定信任你自己class里的所有代码,所以不要打扰私人方法。 你可以决定信任你的程序集的其余部分,所以不用担心内部方法。 你应该几乎可以肯定地validation公共方法的论点。
一个侧面说明: ArgumentNullException
的单参数构造函数重载应该只是参数名称,所以你的testing应该是:
if (s == null) { throw new ArgumentNullException("s"); }
或者,您可以创build一个扩展方法,允许稍微有些挑剔:
s.ThrowIfNull("s");
在我的(通用)扩展方法的版本中,如果它是非空的,我会使其返回原始值,从而允许您编写如下内容:
this.name = name.ThrowIfNull("name");
你也可以有一个超载不参数的名称,如果你不是太困扰。
我同意Jon的意见,但是我想补充一点。
我对什么时候添加明确的空检查的态度是基于这些前提的:
- 应该有一种方法让你的unit testing在程序中执行每一个语句。
-
throw
语句是语句 。 - 一个
if
的结果是一个陈述 。 - 因此,应该有一种方法来在
if (x == null) throw whatever;
如果没有可能的方式执行该语句,则不能进行testing,应该用Debug.Assert(x != null);
replaceDebug.Assert(x != null);
。
如果有可能的方式执行该语句,则编写该语句,然后编写一个unit testing来执行该语句。
公共types的公共方法以这种方式检查他们的论点是特别重要的; 你不知道你的用户会做什么疯狂的事情。 给他们“嘿,你这个笨蛋,你做错了!” 尽快例外。
相反,私人types的私人方法更有可能是在你控制论证的情况下,并且可以有力地保证论证不会为空。 使用一个断言来logging那个不variables。
如果没有明确的检查,如果你不拥有代码,可能很难弄清楚什么是null
。
如果你从一个没有源代码的库里面得到一个NullReferenceException
,你很可能会在确定你做错了什么时遇到很多麻烦。
这些if
检查不会使你的代码明显变慢。
请注意, ArgumentNullException
构造函数的参数是参数名称,而不是消息。
你的代码应该是
if (s == null) throw new ArgumentNullException("s");
我编写了一个代码片段来简化它:
<?xml version="1.0" encoding="utf-8" ?> <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <CodeSnippet Format="1.0.0"> <Header> <Title>Check for null arguments</Title> <Shortcut>tna</Shortcut> <Description>Code snippet for throw new ArgumentNullException</Description> <Author>SLaks</Author> <SnippetTypes> <SnippetType>Expansion</SnippetType> <SnippetType>SurroundsWith</SnippetType> </SnippetTypes> </Header> <Snippet> <Declarations> <Literal> <ID>Parameter</ID> <ToolTip>Paremeter to check for null</ToolTip> <Default>value</Default> </Literal> </Declarations> <Code Language="csharp"><![CDATA[if ($Parameter$ == null) throw new ArgumentNullException("$Parameter$"); $end$]]> </Code> </Snippet> </CodeSnippet> </CodeSnippets>
如果您需要一个更好的方法来确保您没有获取任何空对象作为参数,您可能需要查看代码合同 。
主要的好处是你从一开始就清楚你的方法的要求。 这使得其他开发人员清楚地知道调用者向你的方法发送一个空值是真的错误。
在任何其他代码执行之前,检查也会停止执行该方法。 这意味着您不必担心未完成的方法所做的修改。
当你遇到这个exception时,它可以节省一些debugging。
ArgumentNullException明确声明它是“s”,它是null。
如果你没有这个检查,并让代码吹拂,你会得到一个NullReferenceException从一些不明身份的行在该方法。 在发布版本中,您不会收到行号!
原始代码:
void f(SomeType s) { if (s == null) { throw new ArgumentNullException("s cannot be null."); } // Use s }
重写为:
void f(SomeType s) { if (s == null) throw new ArgumentNullException(nameof(s)); }