開始之前,先想一下,作為C#開發,你在開發過程中遇到的最多的異常是哪個?
不出意外,估計都是空指針引用,ArgumentNullException!
那么有沒有辦法盡量在開發過程檢查出來,而不是等他在運行時報錯?為此,微軟老大哥一直在努力中。
可空上下文
從C#8.0開始,我們可以通過啟用可空上下文,讓VS在開發過程中可以檢查我們出現的空指針引用異常。
啟用可空上下文的方式有兩種:
1、修改.csproj文件,添加<Nullable>enable</Nullable>節點,如:
<PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <Nullable>enable</Nullable> </PropertyGroup>
Nullable節點的值enable表示啟用,disable表示停用。
此外需要注意,這種啟用方式是全局性的,修改默認行為,默認是disable,但是從.net6開始,項目默認是啟用可空上下文的,項目文件.csproj中會默認包含Nullable節點。
2、使用預編譯指令#nullable enable來啟用可空上下文,如:
//enable表示啟用 #nullable enable //disable表示停用 #nullable disable
使用預編譯指令表示局部性啟用,和修改.csproj文件的方式配合使用。而#nullable enable和#nullable disable配合使用可以實現塊級的局部性啟用。
接下來看看可空上下文的用法,比如下面的代碼:
object obj = null; obj.ToString();//throw System.ArgumentNullException
明顯,上面的代碼在運行時會拋出ArgumentNullException,但是在之前,我們在開發過程中得不到任何提示,生成項目也沒有任何提示,但是啟用可空上線文后,我們可以得到warning:

或者生成項目,錯誤窗口也會得到警告:
tip:作為一個開發者,不僅要處理項目中的任何一個異常,也應該正視項目拋出的任何一個警告!
可空引用類型(?)
可空值類型大家應該很熟悉了,它其實是Nullable<T>的對象,它可以認為是T值類型加上null的組合類型,而它的聲明可以簡寫:
//T是值類型,T?本質上是Nullable<T>的簡寫,可以認為是T值類型加上null的組合類型 T?
從C#8.0開始,引入了可空引用類型,它同樣可以按照上面可空值類型的方式進行簡寫,但是此時它就不是Nullable<T>的對象的對象了:
//T是引用類型,T?和Nullable<T>沒有任何關系,T?只在編譯時起作用,在運行時和T的作用是相同 T?
T?和Nullable<T>沒有任何關系,T?只在編譯時起作用,在運行時和T的作用是相同,引入可空引用類型,是為了在可空上下文中更好的檢查空指針引用。
在可空上下文中,我們應該遵循以下原則來開發,假如T是引用類型,那么:
- 如果變量variable1在聲明時使用的類型就是T,那么表示這個variable1是不能為null的引用類型變量,你可以直接使用它(比如調用方法屬性等),而不需要做判斷它是否為null,但是當你將一個null值或者可為null的值(如T?)賦值給這個variable1時,將會拋出警告!
class Program { #nullable enable static void Main(string[] args) { //聲明為不可為null的引用類型 object obj = new object(); //當你將null值賦值時,將會得到警告 obj = null; //直接使用而不需要做判斷它是否為null,但是當你將可空引用進行賦值時,將會得到警告 string str = obj.ToString(); } void Method(object obj)//方法中obj聲明為不可為null的引用類型 { //你可以直接調用它的方法而不需要進行if判斷是否為null int hashcode = obj.GetHashCode(); //直接使用而不需要做判斷它是否為null,但是當你將可空引用進行賦值時,將會得到警告 string str = obj.ToString(); } }
- 如果變量variable1在聲明時使用的類型就是T?,那么表示這個variable1是可為null的引用類型變量,也就是話,當你直接使用它去調用方法屬性時,你需要提前判斷它是否為null,或者使用null 條件運算符調用,否則將會得到警告
class Program { #nullable enable static void Main(string[] args) { //聲明為可為null的引用類型 object? obj = null; //當你直接調用而沒有做判斷是否為null時,將會得到警告 string? str = obj.ToString(); //可以使用null條件運算符調用,沒有警告 string? str1 = obj?.ToString(); } void Method(object? obj)//方法中obj聲明為可為null的引用類型 { //當你直接調用而沒有做判斷是否為null時,將會得到警告 string? str = obj.ToString(); } }
- 如果一個變量variable1在聲明時使用的類型就是T?,但是你在使用時斷定它不會為null,或者你正在將一個T?的變量賦值給T的變量,如果按照上面第1、2點,你需要總是用if判斷,不然又會多出一些警告,很是麻煩,這個時候就可以使用可空容忍(!)來避開它,這個下文再說
可空容忍(!)
其實,如果理解了可空上下文、可空引用類型,可空容忍就好理解了,他其實是一個補充,就是在代碼中,如果我們斷定某個變量在使用時一定不為null,但是編譯器會在可空上下文中拋出警告,這是一個不太正常的行為,可空容忍可以幫助我們消除這種警告,可空容忍可以將不可為空的引用類型轉換成可為空的引用類型,格式:
//T是引用類型,后台跟一個嘆號(!),就是告訴編譯器這個變量一定不為空,這樣可以避免不必要的警告 T!
比如:
class Program { #nullable enable static void Main(string[] args) { //使用null會拋出警告,但是使用可空容忍告訴編譯器消除警告 object obj = null!; //傳入的變量一定不為null Method(new object()); } static void Method(object? obj)//方法中obj聲明為可為null的引用類型 { //這里obj1一定不為null,但是按照可空規則,他將會拋出警告,但是可空容忍可以告訴編譯器它一定不為null,從而消除錯誤的警告 string? str = obj!.ToString(); //可空容忍其實可以認為是將可為空的引用類型轉換為不可為空的引用類型 string str1 = str!; } }
總結
總之,我們只需要記住,可空引用類型是在編譯時起作用,在運行時和普通的引用類型沒有任何區別,它主要是在編譯時結合可空上下文,幫助我們分析代碼中可能出現空指針引用異常的地方,這是一個非常好的語法糖,我們只需要遵守上面三種規則,就可以很大程度減少空指針異常的幾率,其實,如果仔細看的話,.net基礎庫已經遵守了這個規則,比如object類的ToString方法和Equals方法等。
參考文檔:
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-forgiving
