簡體   English   中英

C#:在這種情況下我應該檢查 null 嗎?

[英]C#: Should I bother checking for null in this situation?

可以說我有這種擴展方法:

public static bool HasFive<T>(this IEnumerable<T> subjects)
{
    if(subjects == null)
        throw new ArgumentNullException("subjects");

    return subjects.Count() == 5;
}

你認為這個 null 檢查和異常拋出真的有必要嗎? 我的意思是,當我使用Count方法時,無論如何都會拋出ArgumentNullException ,對嗎?

我可能會想到一個我應該這樣做的原因,但只是想聽聽其他人對此的看法。 是的,我問這個問題的部分原因是懶惰(想盡可能少寫),但也因為我有點認為一堆 null 檢查和異常拋出那種雜亂無章的方法通常最終是兩倍長他們真的需要。 有人應該知道最好不要將 null 發送到方法中:p

無論如何,你們怎么看?


注意: Count()是一種擴展方法,拋出ArgumentNullException ,而不是NullReferenceException 請參閱Enumerable.Count<TSource> Method (IEnumerable<TSource>) 不信你自己試試 =)


注意2:在這里給出的答案之后,我被說服開始更多地檢查 null 值。 不過我還是很懶,所以我開始在Lokad Shared Libraries中使用Enforce class 。 可以推薦看看。 而不是我的例子,我可以這樣做:

public static bool HasFive<T>(this IEnumerable<T> subjects)
{
    Enforce.Argument(() => subjects);
    return subjects.Count() == 5;
}

是的,它會拋出ArgumentNullException 我可以想到兩個原因進行額外檢查:

  • 如果您稍后返回 go 並更改方法以在調用 subject.Count subjects.Count()之前執行某些操作並且忘記在該點進行檢查,那么您可能會在拋出異常之前產生副作用,這不是很好。
  • 目前,堆棧跟蹤將在頂部顯示subjects.Count() ,並且可能帶有帶有source參數名稱的消息。 這可能會讓HasFive的調用者感到困惑,因為他們可以看到subjects參數名稱。

編輯:只是為了節省我不得不在別處再次寫它:

subjects.Count()的調用將拋出ArgumentNullException而不是NullReferenceException Count()是這里的另一種擴展方法,假設正在使用System.Linq.Enumerable中的實現,則記錄(正確)拋出ArgumentNullException 如果您不相信我,請嘗試一下。

編輯:讓這更容易......

如果你做了很多這樣的檢查,你可能想讓這樣做更簡單。 我喜歡以下擴展方法:

internal static void ThrowIfNull<T>(this T argument, string name)
    where T : class
{
    if (argument == null)
    {
        throw new ArgumentNullException(name);
    }
}

問題中的示例方法可以變成:

public static bool HasFive<T>(this IEnumerable<T> subjects)
{
    subjects.ThrowIfNull("subjects");    
    return subjects.Count() == 5;
}

另一種選擇是編寫一個檢查值並返回它的版本,如下所示:

internal static T NullGuard<T>(this T argument, string name)
    where T : class
{
    if (argument == null)
    {
        throw new ArgumentNullException(name);
    }
    return argument;
}

然后你就可以流利地調用它了:

public static bool HasFive<T>(this IEnumerable<T> subjects)
{
    return subjects.NullGuard("subjects").Count() == 5;
}

這對於在構造函數等中復制參數也很有幫助:

public Person(string name, int age)
{
    this.name = name.NullGuard("name");
    this.age = age;
}

(對於不重要的地方,您可能想要一個沒有參數名稱的重載。)

我認為@Jon Skeet 絕對是正確的,但是我想添加以下想法:-

  • 提供有意義的錯誤消息對於調試、日志記錄和異常報告很有用。 BCL 拋出的異常不太可能描述異常 WRT 您的代碼庫的具體情況。 也許這不是 null 檢查的問題(大多數時候)不一定能給你很多特定於域的信息 - “我意外地通過了 null,不知道為什么”幾乎是你能做的最好的但有時您可以提供更多信息,顯然這在處理其他異常類型時更有可能相關。
  • null 檢查清楚地向其他開發人員和您展示了一種文檔形式,如果/當您一年后返回代碼時,可能有人可能會通過 null,如果他們這樣做會有問題
  • 擴展 Jon 的優秀觀點——你可能會在 null 被選中之前做一些事情——我認為參與防御性編程非常重要。 在運行其他代碼之前檢查異常是一種防御性編程形式,因為您考慮到的事情可能不會按您預期的方式工作(或者將來可能會做出您沒想到的更改)並確保無論如何發生(假設您的 null 檢查未刪除)此類問題不會出現。
  • 這是一種運行時斷言,您的參數不是 null。 你可以假設它不是。
  • 上述假設可能會導致代碼更精簡,您在知道參數不是 null 的情況下編寫代碼的 rest,從而減少無關的后續 null 檢查。

在我看來,您應該檢查 null 值。 想到兩件事。

它明確了運行時可能發生的錯誤。

它還使您有機會拋出更好的異常而不是通用的 ArgumentNullException。 因此,使異常的原因更加明確。

您將被拋出的異常將是 Object 引用未設置為 object 的實例。

在追蹤問題時,這不是最有用的異常。

您擁有它的方式將為您提供更多有用的信息,具體說明它是您的主題參考,即 null。

我認為在 function 的頂部進行前置條件檢查是一個好習慣。 也許只是我的代碼充滿了錯誤,但這種做法為我捕獲了很多錯誤。

此外,如果您得到一個帶有參數名稱的 ArgumentNullException(從最相關的堆棧幀中拋出),那么找出問題的根源要容易得多。 此外,您的 function 正文中的代碼可能會隨着時間而改變,因此我不會依賴它來捕捉未來的先決條件問題。

它總是取決於上下文(在我看來)。

例如,在編寫庫(供其他人使用)時,完全檢查每個參數並拋出適當的異常當然是有意義的。

在編寫項目內部使用的方法時,我通常會跳過這些檢查,試圖減少代碼庫的大小。 但即使在這種情況下,也可能存在一個級別(在應用程序層之間),您仍然可以在其中進行此類檢查。 這取決於環境、項目的規模、工作團隊的規模……

對於一個人構建的小項目,這樣做當然沒有意義:)

這取決於具體的方法。 在這種情況下-我認為,如果擴展方法可以處理 null,則不需要異常,並且更好的用法將是。

public static bool HasFive<T>(this IEnumerable<T> subjects) {
    if ( object.ReferenceEquals( subjects, null ) ) { return false; }

    return subjects.Count() == 5;
}

如果您調用“items.HasFive()”並且“items”是 null,那么確實沒有五個項目。

但是如果你有擴展方法:

public static T GetFift<T>(this IEnumerable<T> subjects) {
    ...
}

應該調用“subjects == null”的異常,因為沒有有效的方法,如何處理。

如果您查看 Enumerable class (System.Core.dll) 的源代碼,其中為 IEnumerables 類定義了許多默認擴展方法,您會發現它們都使用 ZDBC11CAA5BDA99F7DZE6FBDABD882ED47 檢查 null 引用

public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> source, int count)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    return SkipIterator<TSource>(source, count);
}

這一點很明顯,但我傾向於遵循我在基本框架庫源中找到的內容,因為您知道這很可能是最佳實踐。

是的,有兩個原因:

首先,IEnumerable 上的其他擴展方法和代碼的使用者可以期望您的代碼也這樣做,但其次,更重要的是,如果您的查詢中有很長的運算符鏈,那么知道哪個引發了異常是有用的信息.

在我看來,應該檢查以后會引發錯誤的已知條件(至少對於公共方法)。 這樣更容易發現問題的根源。

我會提出一個更具信息性的異常,例如:

if (subjects == null)
{
     throw new ArgumentNullException("subjects ", "subjects is null.");
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM