在 2020 年的最後一天,博客園發起了一個開源項目:基於 .NET 的博客引擎 fluss,我抽空把源碼下載下來看了下,發如今屬性的定義中,有不少地方都用到了 null!
,以下圖所示:html
這是什麼用法呢?以前沒有在項目中用過,因此得空就研究了一下。git
之前,!
運算符用來表示 「否」,好比不等於 !=
。在 C# 8.0 之後,!
運算符有了一個新意義—— null
包容運算符,用來控制類型的可空性。要了解 null
包容運算符,首先就要了解可爲 null 的引用類型。github
C# 8.0 引入了可爲 null 的引用類型,與可空類型補充值類型的方式同樣,它們以相同的方式補充引用類型。也就是說,經過將 ?
追加到某引用類型,能夠將變量聲明爲能夠爲 null 的引用類型。 例如,string?
表示能夠爲 null
的 string
。使用這些新類型能夠更清楚地表達代碼設計的意圖 —— 好比將某些變量聲明爲 必須始終具備值,而其餘一些變量聲明爲 能夠缺乏值。編程
藉助這個定義,咱們在定義引用類型的變量或屬性時,便有了兩種選擇:安全
null
。 當變量定義爲不能夠爲 null
時,編譯器會強制執行規則——確保在不檢查它們是否爲 null
的前提下,取消引用這些變量是安全的:
null
值。null
。null
。 當變量定義爲能夠爲 null
時,編譯器會強制執行不一樣的規則——確保您本身已正確檢查 null
引用:
null
時,才能夠取消引用該變量。null
值進行初始化,也能夠在其餘代碼中賦值爲 null
。與 C# 8.0 以前對引用變量的處理相比,這個新功能提供了顯著的優點。在早期版本中,不能經過變量的聲明來肯定設計意圖,編譯器沒有爲引用類型提供針對 null
引用異常的安全性。單元測試
經過添加可爲 null
的引用類型,您能夠更清楚地聲明您的意圖。null
值是表示一個變量不引用值的正確方法,請不要使用此功能從代碼中刪除全部的 null
值。而是,應向編譯器和閱讀代碼的其餘開發人員聲明您的意圖。經過聲明意圖,編譯器會在您編寫與該意圖不一致的代碼時警告您。測試
是否是讀起來有點繞?仍是直接看示例比較容易理解些,請繼續往下看。首先,咱們來ui
有三種方法能夠啓用可爲 null 的引用類型。.net
<Nullable>enable</Nullable>
將上面這一行添加到項目文件中,爲當前項目啓用 可爲 null 的引用類型,以下圖所示:設計
在 Directory.Build.props
文件中能夠爲目錄下的全部項目啓用 可爲 null 的引用類型, 下面截圖是 fluss 項目中的設置:
可使用 #nullable enable
和 #nullable disable
預處理器指令在代碼中的任意位置啓用和禁用 可爲 null 的引用類型:
假設有這個定義:
class Person { public string? MiddleName; }
以下這樣調用:
void LogPerson(Person person) { Console.WriteLine(person.MiddleName.Length); // 警告 CS8602 解引用可能出現空引用。 Console.WriteLine(person.MiddleName!.Length); // 沒有警告 }
這個 !
運算符基本上就是關閉了編譯器的空檢查。
使用此運算符告訴編譯器能夠安全地訪問可能爲 null
的內容。您能夠用它來表達在這種狀況下「不關心」 null
安全性。
當咱們討論到 null
安全性時,一個變量能夠有兩種狀態:
null
。null
。從 C# 8.0 開始,全部的引用類型默認都是 Non-nullable。
「可空性」能夠經過如下兩個新的類型運算符進行修改:
!
:從 Nullable 改成 Non-Nullable?
:從 Non-Nullable 改成 Nullable這兩個運算符是相互對應的。您使用這兩個運算符限定變量,而後編譯器根據您的限定來確保 null
安全性。
?
運算符的用法string? x;
x
是引用類型,所以默認是不能夠爲 null
的。?
運算符將其改成能夠爲 null
的。x = null;
賦值正常,沒有警告。string y;
y
是引用類型,所以默認是不能夠爲 null
的。y = null;
賦值會產生一個警告,由於您給一個聲明爲不支持 null
的變量分配了一個 null
值。以下圖:
!
運算符的用法string x; string? y = null;
x = y;
y
可能爲 null
)。=
左邊是不能夠爲 null
的,但右邊是能夠爲 null
的。x = y!;
=
左右兩邊都是不能夠爲 null
的。y!
使用了 !
運算符到 y
,使得右邊也變成了不能夠爲 null
的,因此賦值沒有問題。以下圖:
⚠️ 警告:
null
包容運算符!
僅在類型系統級別關閉編譯器檢查;在運行時,該值仍然多是null
。
C# 編程時應該儘可能避免使用 null
包容運算符 !
。
有一些有效的使用場景(在下面會介紹),好比單元測試,使用這個運算符是適合的。不過,在 99% 的狀況下,使用替代解決方案會更好。請不要只是爲了取消警告,而在代碼中打幾十個 !
。要想清楚您的場景是否真的值得使用它。
💡 可使用,但要當心使用。若是沒有實際的目的或使用場景,請不要使用它。
null
包容運算符 !
抵消了您得到的編譯器保證的 null
安全性的做用!
使用 !
運算符將致使很難發現 bug。若是您定義了一個標記爲不能夠爲 null
的屬性,您也就假定了能夠安全地使用它。可是在運行時,您卻忽然遇到 NullReferenceException
異常而撓頭,由於一個值在用 !
繞過了編譯器檢查後,實際上卻變成了 null
,這不是給本身添麻煩嗎?
既然這樣,那麼,
!
運算符會存在?null
的。null
。null
時的代碼行爲。接下來,咱們繼續看下:
null!
是什麼意思呢?null!
是在告訴編譯器 null
不是 null
值,這聽起來很怪異,是否是?
實際上,它和上面例子中的 y!
同樣。它只是看起來挺怪異,由於您將該運算符用在了 null
字面量上,但概念是同樣的。
咱們再來看一下文章開頭提到的 fluss 源碼中的一行代碼:
/// <summary> /// 所屬的博客。 /// </summary> public BlogSite BlogSite { get; set; } = null!;
這行代碼定義了一個名稱爲 BlogSite
、類型爲 BlogSite
的不能夠爲 null
的類屬性。由於它是不能夠爲 null
的,所以單從技術上講,很明顯它是不能夠被賦值爲 null
的。
可是,您能夠經過使用 !
運算符,將 BlogSite
屬性賦值爲 null
。由於,就編譯器所關心的 null
安全性而言,null!
不是 null
。
看到這裏,想必您確定已經明白了 null!
是什麼意思,也學會了 null
包容運算符 !
的概念、由來和用法。可是正如我在文中提到的那樣,編程時應該儘可能避免使用 !
,由於它抵消了您本能夠得到的編譯器保證的 null
安全性;並且,這種寫法閱讀起來有點讓人費解。
參考:
做者 : 技術譯民
出品 : 技術譯站