C#中的空参数检查

kaa*_*lus 75 c# null function

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

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

  // Use s
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 146

是的,有充分的理由:

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

至于你的反对意见:

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

并为你的断言:

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

真?考虑:

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

这用s,但它没有抛出异常.如果它为snull无效,并且表明出现了错误,则异常是最合适的行为.

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

旁注:单参数构造函数重载ArgumentNullException应该只是参数名,所以你的测试应该是:

if (s == null)
{
  throw new ArgumentNullException("s");
}
Run Code Online (Sandbox Code Playgroud)

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

s.ThrowIfNull("s");
Run Code Online (Sandbox Code Playgroud)

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

this.name = name.ThrowIfNull("name");
Run Code Online (Sandbox Code Playgroud)

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

  • @kaalus:你对测试采用同样的态度吗?"我的测试不会捕获所有可能的错误,因此我不会写任何"?当安全机制*起作用时,它们将更容易找到问题并减少其他地方的影响(通过更早地解决问题).如果这种情况发生在10次中的9次,那么仍然比10次中的0次更好...... (8认同)
  • +1表示扩展方法.我做了完全相同的事情,包括其他验证,例如`ICollection`上的`ThrowIfEmpty` (7认同)
  • 为什么我认为维护起来比较困难:与每次需要程序员干预的每种手动机制一样,这很容易出现错误和遗漏,并使代码混乱.片状安全性比没有安全性更差. (4认同)
  • @DoctorOreo:我不使用`Debug.Assert`.捕获生产中的错误(在他们搞砸真实数据之前)比开发中更重要. (2认同)
  • 现在使用C#6.0,我们甚至可以使用``引发新的ArgumentNullException(nameof(s))`` (2认同)

Eri*_*ert 47

我同意乔恩,但我会补充一点.

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

  • 您的单元测试应该有一种方法来执行程序中的每个语句.
  • throw陈述是陈述.
  • if一个陈述的后果.
  • 因此,应该行使的方式throwif (x == null) throw whatever;

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

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

这是特别是公共类型的公共方法,检查他们的论点以这种方式很重要; 你不知道你的用户会做什么疯狂的事情.给他们"嘿,你笨蛋,你做错了!" 尽快例外.

相反,私有类型的私有方法更可能出现在您控制参数的情况下,并且可以强有力地保证参数永远不为空; 使用断言来记录该不变量.


SLa*_*aks 8

如果没有一个明确的if检查,它可以是很难搞清楚什么null,如果你没有自己的代码.

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

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


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

if (s == null) throw new ArgumentNullException("s");
Run Code Online (Sandbox Code Playgroud)

我编写了一个代码片段来简化这个过程:

<?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>
Run Code Online (Sandbox Code Playgroud)


gld*_*ael 7

我已经使用了一年了:

_ = s ?? throw new ArgumentNullException(nameof(s));
Run Code Online (Sandbox Code Playgroud)

这是一个单行,而throw(_)表示没有不必要的分配。


AD.*_*Net 5

如果您需要一种更好的方法来确保不会将任何空对象作为参数,您可能需要查看代码契约