71

假设我有一个将某种对象作为参数的方法。现在说如果这个方法传递了一个空参数,这是一个致命错误,应该抛出一个异常。我是否值得编写这样的代码(记住这是一个简单的例子):

void someMethod(SomeClass x)
{
    if (x == null){
        throw new ArgumentNullException("someMethod received a null argument!");
    }

    x.doSomething();
}

或者当它调用 x.doSomething() 时仅仅依靠它抛出 NullException 对我来说是安全的吗?

其次,假设 someMethod 是一个构造函数,并且 x 在调用另一个方法之前不会被使用。我应该立即抛出异常还是等到需要 x 后再抛出异常?

4

14 回答 14

68

我更喜欢不检查参数会提供的ArgumentNullExceptionNullReferenceException一般来说,我的偏好是在尝试调用可能为空的对象上的方法之前始终检查是否为空。

如果该方法是一个构造函数,那么它将取决于几个不同的因素:该属性是否还有一个公共设置器以及该对象实际被使用的可能性有多大。如果有一个公共的 setter,那么不通过构造函数提供一个有效的实例是合理的并且不应该导致异常。

如果没有公共设置器并且可以在不引用注入对象的情况下使用包含对象,您可能希望推迟检查/异常直到尝试使用它。不过,我认为一般情况是注入的对象对于实例的运行至关重要,因此 ArgumentNull 异常是完全合理的,因为没有它实例就无法运行。

于 2008-12-15T15:46:10.840 回答
38

我总是遵循快速失败的做法。如果您的方法依赖于 X,并且您了解 X 可能以 null 形式传递,请对其进行 null 检查并立即引发异常,而不是延长故障点。

2016 年更新:

现实世界的例子。我强烈推荐使用JetBrains Annotations

[Pure]
public static object Call([NotNull] Type declaringType, 
                          [NotNull] string methodName, 
                          [CanBeNull] object instance)
{
    if (declaringType == null) throw new ArgumentNullException(nameof(declaringType));
    if (methodName == null) throw new ArgumentNullException(nameof(methodName));

使用 C# 6 提供了运算符,Guard 语句得到了极大的改进nameof

于 2008-12-15T15:46:12.243 回答
15

我更喜欢显式异常,原因如下:

  • 如果该方法有多个 SomeClass 参数,它让您有机会说出它是哪一个(调用堆栈中的所有其他内容都可用)。
  • 如果你在引用 x 之前做了一些可能有副作用的事情怎么办?
于 2008-12-15T15:46:57.140 回答
13

这些天没有理由不检查。C# 继续前进,您可以使用丢弃和空合并运算符非常巧妙地做到这一点:

_ = declaringType ?? throw new ArgumentNullException(nameof(declaringType));
_ = methodname ?? throw new ArgumentNullException(nameof(methodName));
于 2020-03-11T10:58:46.363 回答
12

我同意快速失败的想法——但是知道为什么快速失败是可行的是明智的。考虑这个例子:

void someMethod(SomeClass x)
{       
    x.Property.doSomething();
}

如果你依靠NullReferenceException来告诉你出了什么问题,你怎么知道什么是空的?堆栈跟踪只会给你一个行号,而不是哪个引用为空。在这个例子中x,或者x.Property两者都为空,并且事先没有通过积极的检查快速失败,您将不知道它是哪个。

于 2008-12-15T18:28:14.803 回答
9

我也更喜欢使用显式 ArgumentNullException 进行参数检查。

查看元数据:

 //
    // Summary:
    //     Initializes a new instance of the System.ArgumentNullException class with
    //     the name of the parameter that causes this exception.
    //
    // Parameters:
    //   paramName:
    //     The name of the parameter that caused the exception.
    public ArgumentNullException(string paramName);

您可以看到,该字符串应该是参数的名称,即为 null,因此向开发人员提示出了什么问题。

于 2008-12-15T15:50:39.270 回答
6

如果您期望输入不为空,则应显式抛出 ArgumentNullException。您可能想编写一个名为 Guard 的类,为此提供帮助方法。所以你的代码将是:

void someMethod(SomeClass x, SomeClass y)
{
    Guard.NotNull(x,"x","someMethod received a null x argument!");
    Guard.NotNull(y,"y","someMethod received a null y argument!");


    x.doSomething();
    y.doSomething();
}

NonNull 方法将执行空值检查并抛出 NullArgumentException 以及调用中指定的错误消息。

于 2008-12-15T15:50:35.417 回答
5

最好尽早抛出 ArgumentNullException。如果你抛出它,你可以提供比 NullReferenceException 更有用的问题信息。

于 2008-12-15T15:48:30.240 回答
4
  1. 如果您不想要 Null 值,请明确执行此操作。否则,当其他人查看您的代码时,他们会认为传递 Null 值是可以接受的。

  2. 尽早做。这样,您就不会在不应该传播 Null 的“错误”行为时传播它。

于 2008-12-15T15:49:07.573 回答
3

如果您进行防御性编程,则应该快速失败。因此,请在代码开头检查您的输入和错误。你应该善待你的来电者,并尽可能给他们最具描述性的错误信息。

于 2008-12-15T15:47:20.680 回答
3

我可能会因此而被否决,但我认为完全不同。

遵循一种称为“从不传递 null”的良好做法并删除丑陋的异常检查怎么样?

如果参数是对象,请勿传递 NULL。此外,请勿返回 NULL。您甚至可以使用 Null 对象模式来帮助解决这个问题。

如果它是可选的,请使用默认值(如果您的语言支持它们)或创建重载。

比丑陋的例外要干净得多。

于 2010-10-05T14:32:58.070 回答
1

您可以使用如下语法来不仅抛出ArgumentNullException异常,还可以将该异常命名为参数作为其错误文本的一部分。例如;

void SomeMethod(SomeObject someObject)
{
    Throw.IfArgNull(() => someObject);
    //... do more stuff
}

用于引发异常的类是;

public static class Throw
{
    public static void IfArgNull<T>(Expression<Func<T>> arg)
    {
        if (arg == null)
        {
            throw new ArgumentNullException(nameof(arg), "There is no expression with which to test the object's value.");
        }

        // get the variable name of the argument
        MemberExpression metaData = arg.Body as MemberExpression;
        if (metaData == null)
        {
            throw new ArgumentException("Unable to retrieve the name of the object being tested.", nameof(arg));
        }

        // can the data type be null at all
        string argName = metaData.Member.Name;
        Type type = typeof(T);
        if (type.IsValueType && Nullable.GetUnderlyingType(type) == null)
        {
            throw new ArgumentException("The expression does not specify a nullible type.", argName);
        }

        // get the value and check for null
        if (arg.Compile()() == null)
        {
            throw new ArgumentNullException(argName);
        }
    }
}
于 2017-07-19T16:30:54.457 回答
1

我非常同意@tvanfosson。除了他的回答之外,使用 .net 6 很容易抛出ArgumentNullException.

ArgumentNullException.ThrowIfNull(object);

这是官方文档。

于 2021-10-16T14:02:24.433 回答
0

所有代码示例都使用容易出错的 IF 子句,其中一个使用 equal operator ==

如果类型覆盖会发生什么==

在 C# 7 及更高版本上,使用常量模式匹配。

例子:

if (something is null) 
{
    throw new ArgumentNullException(nameof(something), "Can't be null.");
}
于 2021-08-11T11:47:19.553 回答