簡體   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