每次建置項目都出現這麼多編譯警告

时间:2021-07-06 04:30:33

溫馨提醒:本文提及的 C# 8 新成果雖已通過提案,但不代表將來 C# 8 正式發布時必然會納入。這暗示我這篇筆記有可能白寫了,也暗示您不必急著瞭解這項新成果的所有細節,可能只要瞄一下底下的「提要」說明就夠了。

提要

C# 8 的 Nullable Reference Types 意味著往後所有的參考型別預設都是不成為 null;對於可為 null 的參考型別變數,寫法跟可為 null 的實質型別一樣,宣告時必須在型別後面加上 "?" 字元。請看以下範例:

1 int? num = null; // 可為 null(實質型別) 2 string? userName = null; // 可為 null(參考型別) 3 string password = null; // 編譯警告: 不成為 null! 4 Employee? emp = null; // 可為 null(參考型別) 5 User user = null; // 編譯警告: 不成為 null!

這項新成果有不少細節,不容易幾句話帶過。所以,接下來是比較冗長、也比較詳細的說明。

內容大綱:

基礎知識

Nullable Reference Types 是什麼?

為什麼需要 Nullable Reference Types?

不允許 Null 的參考型別
- default 暗示式

允許 Null 的參考型別
- 靜態路徑分析

安裝工具
- Sharplab.io
- 安裝擴充成果:Nullable Reference Types Preview  

 編譯既有項目
- 調整編譯器的警告   

基礎知識

一直以來,C# 開發人員已經很熟悉實質型別(value types)和參考型別(reference types)所代表的意義,以及它們之間的差異:

在宣告一個實質型別的變數時(例如 int i),如果沒有賦予初始值,那麼編譯器會將它設定成該型別的預設值。例如 int 型別的預設值是 0。

如果要讓實質型別的變數值可為 null,則要使用泛型結構  System.Nullable<T> ,此中的 T 必須是實質型別。此泛型結構在 C# 中可以用更簡便的 T? 語法來暗示,例如 int? 等同於 Nullable<int>,代表可為 null 的整數。

在宣告一個參考型別的變數時(例如 string s),如果沒有賦予初始值,那麼它的預設值等于 null。也就是說,參考型別的變數是可以為 null 的。

那麼,既然參考型別本來就是可以是 null,C# 8 (計畫)新增的成果裡面怎麼會有一項叫做「可為空的參考型別」(Nullable Reference Types)呢?它究竟是什麼?

註:如果您想一邊閱讀,一邊把程式碼敲進去實驗看看,可以先跳到後面,找到有關安裝 C# 8 實驗性擴充成果的說明。

Nullable Reference Types 是什麼?

簡單地說,在 C# 8 以前,參考型別都是可為 null 的,這點到了 C# 8 將開始改變:所有參考型別預設都是不成為 null;如果想要讓某個參考型別的變數可以為 null,則必須明確宣告它「可以為空」。請看以下範例:

string s1 = null; // C# 7.x OK,C# 8 出現編譯警告。 string? s2 = null; // C# 7.x 編譯掉敗,C# 8 O

第 1 行程式碼在 C# 8 會出現下列編譯警告:

Warning CS8600: Cannot convert null to non-nullable reference.

因為 string 是參考型別,而就如剛才提過的,參考型別到了 C# 8 已經改成預設不成為空,故編譯器會發出警告,告訴你不能把 null 指派給「不成為 null 的參考」。

另一方面,第 2 行程式碼在 C# 8 編譯沒問題,若使用 C# 7.2 或更早之前的編譯器,則會出現編譯錯誤:

error CS0453: The type ‘string’ must be a non-nullable value type in order to use it as parameter ‘T’ in the generic type or method ‘Nullable<T>’

這是因為  string?  是 C# 8 的 Nullable Reference Types 語法,暗示要宣告一個可為 null 的字串變數。C# 7.2 沒有這種語法,便會出現編譯錯誤。

如您所見,「可為空的參考型別」跟「可為空的實質型別」的語法到了 C# 8 已經統一了:宣告時在型別後面加個問號(’?’)。

為什麼需要 Nullable Reference Types?

在 C# 8 之前,由於參考型別的變數值有兩種可能:null 或者不是 null,編譯器在這裡幫不上忙,所以在處理參考型別的變數時,我們每每會加上一些「防護罩」,也就是預先檢查變數是否為 null,例如:

1 static int Length(string text) 2 { 3 return text == null? 0 : text.Length; 4 }