抛出 ArgumentNullException

假设我有一个方法,它接受某种类型的对象作为参数。现在假设这个方法被传递了一个 null 参数,这是一个致命的错误,应该抛出一个异常。对我来说编写这样的代码是否值得(请记住这只是一个小例子) :

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


x.doSomething();
}

还是仅仅依靠它在调用 x.doSomething ()时抛出 NullException 是安全的?

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

106723 次浏览

相对于不检查参数的 NullReferenceException,我更喜欢 ArgumentNullException。一般来说,在尝试调用可能为空的对象上的方法之前,我倾向于总是检查 null。

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

如果没有公共 setter,并且可以在不引用注入对象的情况下使用包含对象,那么您可能希望将检查/异常推迟到尝试使用它之后。但是,我认为一般情况下,注入的对象对于实例的功能是必不可少的,因此 ArgumentNull 异常是完全合理的,因为没有它,实例就无法正常工作。

我一直遵循 很快就会失败的做法。如果您的方法依赖于 X,并且您知道 X 可能以 null 形式传递,那么检查它为 null 并立即引发异常,而不是延长故障点。

2016年更新:

我强烈推荐使用 JetBrains 注释

[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操作员,警卫语句得到了极大的改进。

我更喜欢明确的例外,原因如下:

  • 如果这个方法有多个 Some Class 参数,那么它就给了您一个机会来判断它是哪个参数(调用堆栈中的其他所有参数都是可用的)。
  • 如果在引用 x 之前做了一些可能有副作用的事情怎么办?

如果您编写防御性程序,那么应该很快就会失败。因此,请在代码开始时检查输入并排除错误。您应该对您的调用者友好,并尽可能给他们最具描述性的错误消息。

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

  1. 如果不希望使用 Null 值,则显式执行此操作。否则,当其他人查看您的代码时,他们会认为传递 Null 值是可以接受的。

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

如果希望输入不为空,则应显式引发 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 进行参数检查。

查看元数据:

 //
// 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,因此可以向开发人员提示出错的原因。

我同意快速失败的观点——然而,知道为什么快速失败是可行的是明智的。考虑一下这个例子:

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

如果您依赖于 NullReferenceException来告诉您有什么地方出错了,那么您如何知道什么是 null?堆栈跟踪只会给出一个行号,而不会给出哪个引用为 null。在这个例子中,xx.Property可能都是 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);
}
}
}

这年头没理由不付账。C # 继续前进,你可以很巧妙地使用一个丢弃操作符和一个空合并操作符:

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

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

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

在 C # 7或更高版本中,使用恒定的模式匹配。

例如:

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

我非常同意@tvanfoson 的观点。对于他的回答,使用.net 6很容易抛出 ArgumentNullException

ArgumentNullException.ThrowIfNull(object);

这是官方文件。

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

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

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

Https://learn.microsoft.com/en-us/dotnet/api/system.argumentnullexception.throwifnull?view=net-6.0