使用可為 Null 的參考型別更新程式碼基底,以改善 Null 診斷警告

您能利用可為 null 的參考型別,宣告是否應該或不應該為參考型別的變數,指派 null 值。 當您的程式碼可能取值 null 時,編譯器的靜態分析和警告,是這項功能最重要的優點。 啟用之後,編譯器會產生警告,協助您避免在執行程式碼時,擲回 System.NullReferenceException

如果程式碼基底相對較小,可以開啟專案中的功能、解決警告,並能享有較佳之診斷的優點。 程式碼基底若比較大,可能需要更加結構化的方法來處理一段時間的警告,當您處理不同類型或檔案的警告時,請啟用此功能。 本文說明更新程式碼基底的不同策略,以及與這些策略相關聯的取捨。 開始移轉之前,請先閱讀可為 null 參考型別的概念性概觀。 它涵蓋編譯器的靜態分析、可能為nullnot-nullNull狀態值,以及可為 Null 的註釋。 您一旦熟悉這些概念和詞彙之後,即可開始移轉程式碼。

規劃移轉

無論您的程式碼基底如何更新,目標都是要在專案中啟用警告可為 null 和註釋可為 null。 達到該目標之後,您的專案中就會有 <nullable>Enable</nullable> 設定。 不需要任何前置處理指示詞,來調整其他地方的設定。

第一個選擇是設定專案的預設值。 您的選擇有:

  1. 以停用可為 null 作為預設值:如果未對專案檔新增Nullable 元素,則預設值是「停用」。 未主動對程式碼基底新增新的檔案時,請使用此預設值。 主要的活動是會更新程式庫,以使用可為 null 的參考型別。 使用此預設值表示當您更新其程式碼時,會對每個檔案新增可為 null 的前置處理指示詞。
  2. 以啟用可為 null 作為預設值:當您主動開發新功能時,請設定此預設值。 您會希望所有新的程式碼,都能受益於可為 null 的參考型別和可為 null 的靜態分析。 使用此預設值表示您必須對每個檔案的頂端,新增 #nullable disable。 當您處理每個檔案中的警告時,將會移除這些前置處理指示詞。
  3. 以警告可為 null 作為預設值:請為兩階段移轉選擇此預設值。 在第一個階段,處理警告。 在第二個階段中,開啟註釋,以宣告變數應有的「null 狀態」。 使用此預設值表示您必須對每個檔案的頂端,新增 #nullable disable
  4. 註釋可為 null 作為預設值。 在解決警告之前先標註程式碼。

以啟用可為 null 作為預設值,會讓事前的作業增加,要對每個檔案新增前置處理指示詞。 其優點是,每個新增至專案的新程式碼檔案,都會啟用可為 null。 所有新的工作都會認為 null;只需要更新現有的程式碼。 以停用可為 null 作為預設值,若程式庫穩定,則運作效果較佳,且開發的主要重點可以放在採用可為 null 的參考型別。 當您標註 API 時,可以開啟參考型別可為 null。 完成時,可以為整個專案啟用參考型別可為 null。 當您建立新的檔案時,必須新增前置處理指示詞,並使其能認得 null。 如果您的小組有任何開發人員忘記,新程式碼現在會處於工作的待辦項目狀態,讓所有程式碼都能認得 null。

要挑選其中哪一個策略,取決於專案中進行多少主動開發。 專案愈成熟和穩定,就愈適合採行第二個策略。 要開發的功能愈多,就愈適合第一個策略。

重要

內容全域可為 null 不適用於產生的程式碼檔案。 不論採用任何一個策略,所有標記為產生的來源檔案,都會「停用」內容可為 null。 這表示產生的檔案中的所有 API,都不會有標註。 有四種方式可將檔案標記為是產生的檔案:

  1. 在 .editorconfig 中,於套用至該檔案的區段中,指定 generated_code = true
  2. <auto-generated><auto-generated/> 置於檔案頂端的註解中。 它可以在註解的任一行,但註解區塊必須是檔案中的第一個元素。
  3. 使用 TemporaryGeneratedFile_ 做為檔案名稱的開頭
  4. 使用 .designer.cs.generated.cs.g.cs.g.i.cs 做為檔案名稱的結尾。

產生器可以選擇使用 #nullable 前置處理指示詞。

了解內容和警告

啟用警告和註釋,可控制編譯器如何檢視參考型別和是否可為 null。 每個類型都可以是三個可為 null 的其中之一:

  • 不處理:停用註釋內容時,所有參考型別對於是否可為 null,都「不處理」
  • 不可為 Null:啟用註釋內容時,未加註釋的參考型別 C 會是「不可為 null」
  • 可為 null:加註註釋的參考型別 C?,會是「可為 null」,但停用註釋內容時,會發出警告。 啟用註釋內容時,以 var 宣告的變數「可為 null」

編譯器會根據該是否可為 null 來產生警告:

  • 如果對其指派了可能的 null 值,「不可為 null」類型會引發警告。
  • 如果在「可能為 null」的情況下,對其取值,「可為 null」類型會引發警告。
  • 在「可能為 null」且已啟用警告內容時,對其取值,「不處理」類型會引發警告。

每個變數都有預設的可為 null 狀態,取決於其是否可為 null:

  • 可為 null 變數的預設「null 狀態」為「可能為 null」
  • 不可為 null 變數的預設「null 狀態」為「不可為 null」
  • 不處理是否可為 null 的變數,預設「null 狀態」為「不可為 null」

啟用可為 null 的參考型別之前,程式碼基底中的所有宣告都是「不處理是否可為 null」。 這之所以重要是因為這表示所有參考型別的預設「null 狀態」都是「不可為 null」

處理警告

如果專案使用 Entity Framework Core,應閱讀其位於使用可為 null 參考型別上的指導。

開始移轉時,應該從先只啟用警告開始。 所有宣告都會保持「不處理是否可為 Null」的狀態,但是當您在其「null 狀態」變更為「可能為 null」之後進行取值時,就會看到警告。 解決這些警告時,要檢查更多位置處的 null,然後您的程式碼基底復原性會更佳。 若要了解不同情況下特定的技術,請參閱解決可為 null 警告的技術一文。

您可以先解決警告,並在每個檔案或類別中啟用註釋,再繼續進行其他程式碼。 不過,在啟用型別註解之前,在內容為「警告」時所產生的警告,通常更有效率。 如此一來,所有類型都「不會處理」,直到您解決第一組的警告為止。

啟用型別註解

解決完第一組警告之後,可以啟用「註釋內容」。 這樣會將參考型別從「不處理」變更為「不可為 null」。 所有以 var 宣告的變數,都「可為 null」。 這項變更通常會引發新的警告。 解決編譯器警告的第一個步驟,是對參數使用 ? 註釋,並傳回可指出引數或傳回值可能是 null 的類型。 當您執行這項工作時,目標不只是修正警告。 更重要的目標是讓編譯器了解您對於可能的 null 值之意圖。

屬性擴充型別註解

已新增了一些屬性來表達變數 null 狀態的其他資訊。 API 的規則比起所有參數和傳回值的「不可為 null」或「可能為 null」,來得複雜的多。 許多 API 的規則都更為複雜,適用於變數可以或不可以是 null 時。 在這些情況下,將要使用屬性來表達這些規則。 描述 API 語意的屬性,請見影響可為 null 分析的屬性一文。

下一步

啟用註釋,然後解決所有警告之後,可以將專案的預設內容設定為「啟用」。 如果可為 null 註釋或警告內容的任何程式碼中,新增了編譯指引 (pragma),可加以移除。 經過一段時間之後,會看到新的警告。 您可以撰寫引發警告的程式碼。 可為 null 的參考型別之程式庫相依性,可能會有所更新。 而這些更新會將該程式庫中的類型,從「不處理」變更為「不可為 null」或「可為 null」

您也可以從 C# 中可 null 安全性的 Learn 課程模組,探索這些概念。