繁体   English   中英

抛出 ArgumentNullException

[英]Throwing ArgumentNullException

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

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

    x.doSomething();
}

还是仅依靠它在调用 x.doSomething() 时抛出 NullException 对我来说是否安全?

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

我更喜欢ArgumentNullException而不是NullReferenceException不检查参数会提供。 通常,我的偏好是在尝试调用可能为空的对象上的方法之前始终检查空性。

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

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

我总是遵循快速失败的做法。 如果您的方法依赖于 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 提供了nameof运算符,Guard 语句得到了极大的改进。

我更喜欢显式例外,原因如下:

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

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

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

如果您依靠NullReferenceException来告诉您出了什么问题,您怎么知道什么是 null? 堆栈跟踪只会给你一个行号,而不是哪个引用为空。 在此示例中, xx.Property可能都为空,并且在事先进行积极检查的情况下不会快速失败,您将不知道它是哪个。

我也更喜欢使用显式 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);

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

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

_ = declaringType ?? throw new ArgumentNullException(nameof(declaringType));
_ = methodname ?? throw new ArgumentNullException(nameof(methodName));

如果您希望输入不为空,则应明确抛出 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 和调用中指定的错误消息。

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

  1. 如果您不想要 Null 值,请明确执行此操作。 否则,当其他人查看您的代码时,他们会认为传递 Null 值是可以接受的。

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

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

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

遵循称为“从不传递空值”的良好实践并删除丑陋的异常检查怎么样?

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

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

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

您可以使用如下语法不仅抛出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);
        }
    }
}

由于 .NET 6 你可以在一行中抛出参数 null 异常,例如:

int? foo = null;
ArgumentNullException.ThrowIfNull(foo);

这将检查foo是否为 null 并抛出错误。 您可以传递第二个参数来设置参数名称,但不建议这样做。

https://learn.microsoft.com/en-us/do.net/api/system.argumentnullexception.throwifnull?view.net-6.0

所有代码示例都使用容易出错的 IF 子句,其中一个使用相等运算符==

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

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

例子:

if (something is null) 
{
    throw new ArgumentNullException(nameof(something), "Can't be null.");
}

我非常同意@tvanfosson。 添加到他的答案中,使用 .net 6 很容易抛出ArgumentNullException

ArgumentNullException.ThrowIfNull(object);

这是官方文档。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM