C#中的空参数检查

时间:2011-09-28 15:19:32

标签: c# function null

在C#中,是否有任何好的理由(除了更好的错误消息)将参数空值检查添加到null不是有效值的每个函数?显然,使用s的代码无论如何都会抛出异常。这样的检查使代码变得更慢,更难维护。

void f(SomeType s)
{
  if (s == null)
  {
    throw new ArgumentNullException("s cannot be null.");
  }

  // Use s
}

9 个答案:

答案 0 :(得分:142)

是的,有充分的理由:

  • 它确切地标识了什么是null,这在NullReferenceException
  • 中可能并不明显
  • 即使某些其他条件意味着该值未被取消引用,它也会使代码在无效输入上失败
  • 之前发生异常该方法可能会在第一次取消引用之前遇到任何其他副作用
  • 这意味着您可以放心,如果您将参数传递给其他人,则不会违反他们的合同
  • 它记录了您方法的要求(当然,使用Code Contracts更好)

至于你的反对意见:

  • 速度较慢:您是否发现实际是您代码中的瓶颈,或者您在猜测?无效检查非常快,在绝大多数情况下,它们将成为瓶颈
  • 它使代码更难维护:我认为相反。我认为更容易使用代码可以清楚地表明参数是否为空,以及您确信该条件是强制执行的。

对于你的断言:

  

显然,使用s的代码无论如何都会抛出异常。

真的?考虑:

void f(SomeType s)
{
  // Use s
  Console.WriteLine("I've got a message of {0}", s);
}

使用s,但它不会抛出异常。如果s无效为空,并且表明出现了问题,则异常是最合适的行为。

现在其中你放置那些参数验证检查是另一回事。您可能决定信任自己类中的所有代码,因此不必担心私有方法。您可能决定信任组件的其余部分,因此不必担心内部方法。您几乎肯定会验证公共方法的参数。

附注:ArgumentNullException的单参数构造函数重载应该只是参数名称,因此您的测试应该是:

if (s == null)
{
  throw new ArgumentNullException("s");
}

或者你可以创建一个扩展方法,允许有点terser:

s.ThrowIfNull("s");

在我的(通用)扩展方法的版本中,如果它是非null,我会返回原始值,允许你编写如下内容:

this.name = name.ThrowIfNull("name");

如果您对此不太感兴趣,也可以使用不带参数名称的重载。

答案 1 :(得分:45)

我同意Jon,但我会补充一点。

我对何时添加显式空检查的态度是基于这些前提:

  • 您的单元测试应该有一种方法可以在程序中执行每个语句。
  • throw语句是语句
  • if的后果是声明
  • 因此,应该有一种方法可以在throw
  • 中练习if (x == null) throw whatever;

如果没有可能的方式要执行该语句,则无法对其进行测试,应将其替换为Debug.Assert(x != null);

如果有可能的方法来执行该语句,那么编写语句,然后编写一个单元测试来运行它。

公共类型的公共方法以这种方式检查它们的参数是非常重要的 。你不知道你的用户会做什么疯狂的事情。给他们“嘿,你笨蛋,你做错了!”尽快例外。

相比之下,私有类型的私有方法更有可能处于控制参数的情况下,并且可以强有力地保证参数永远不为空;使用断言记录该不变量。

答案 2 :(得分:7)

如果您没有明确的if检查,如果您不拥有代码,则很难找出 null

如果您从没有源代码的库中深入了解NullReferenceException,那么您可能很难弄清楚您做错了什么。

这些if检查不会使您的代码明显变慢。


请注意,ArgumentNullException constructor的参数是参数名称,而不是消息 你的代码应该是

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>

答案 3 :(得分:4)

如果您需要更好的方法来确保不将任何null对象作为参数,您可能需要查看Code Contracts

答案 4 :(得分:3)

我已经使用了一年了:

select joining_column, count(joining_col) from <tablename>
group by joining_col

这是一个oneliner,丢弃(_ = s ?? throw new ArgumentNullException(nameof(s)); )意味着没有不必要的分配。

答案 5 :(得分:2)

主要的好处是,您从一开始就明确了解方法的要求。这使得处理代码的其他开发人员明白,调用者向您的方法发送空值确实是一个错误。

在执行任何其他代码之前,检查还将暂停执行该方法。这意味着您不必担心未完成的方法所做的修改。

答案 6 :(得分:2)

当你遇到异常时,它会保存一些调试。

ArgumentNullException明确声明它是“s”,它是null。

如果你没有那个检查并且让代码爆破op,你会从该方法中的某个未识别的行获得NullReferenceException。在发布版本中,您不会获得行号!

答案 7 :(得分:0)

commit()

所以对你的例子来说:

int i = Age ?? 0;

或者:

if (age == null || age == 0)

或者:

if (age.GetValueOrDefault(0) == 0)

或三元:

if ((age ?? 0) == 0)

答案 8 :(得分:-1)

原始代码:

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));
}

[编辑]使用nameof重写的原因是因为它允许更容易的重构。如果变量s的名称发生了变化,那么调试消息也会更新,而如果您只是硬编码变量的名称,那么随着时间的推移,它最终会过时。这是工业中使用的一种很好的做法。