了解可 Null 性
如果您是 .NET 開發人員,您可能會遇到 System.NullReferenceException。 這是在執行階段取值 null
,也就是在執行階段評估變數時發生,但變數會參考 null
。 此例外狀況到目前為止是 .NET 生態系統內最常發生的例外狀況。 null
的建立者 (Sir Tony Hoare) 將 null
稱為「代價數十億的錯誤」。
在下列範例中,FooBar
變數會指派給 null
並立即取值,因而出現問題:
// Declare variable and assign it as null.
FooBar fooBar = null;
// Dereference variable by calling ToString.
// This will throw a NullReferenceException.
_ = fooBar.ToString();
// The FooBar type definition.
record FooBar(int Id, string Name);
當您應用程式的大小和複雜度增加時,開發人員會變得更難以找出問題。 找出此類潛在錯誤是一項工具作業,而 C# 編譯器在這裡可提供協助。
定義 Null 安全性
「Null 安全性」一詞會定義一組專屬於可為 Null 的類型特徵,以協助減少可能的 NullReferenceException
發生次數。
考慮上述 FooBar
範例,您可以藉由檢查 fooBar
變數在取值之前是否為 null
來避免 NullReferenceException
:
// Declare variable and assign it as null.
FooBar fooBar = null;
// Check for null
if (fooBar is not null)
{
_ = fooBar.ToString();
}
// The FooBar type definition for example.
record FooBar(int Id, string Name);
為了協助識別此類案例,編譯器可以推斷程式碼的意圖,並強制執行所需的行為。 不過,只有在啟用 [可為 Null 的內容] 時,才會發生這種情況。 在討論 [可為 Null 的內容] 之前,讓我們先描述可能的可為 Null 的類型。
可為 Null 的類型
在 C# 2.0 之前,只有參考型別可為 Null。 int
或 DateTime
之類的值類型「不能」為 null
。 如果這些類型在沒有值的情況下初始化,則會回復為其 default
值。 如果是 int
,則為 0
。 針對 DateTime
,則為 DateTime.MinValue
。
具現化而不具初始值的參考類型會以不同方式運作。 所有參考類型的 default
值都是 null
。
請考慮下列 C# 程式碼片段:
string first; // first is null
string second = string.Empty // second is not null, instead it's an empty string ""
int third; // third is 0 because int is a value type
DateTime date; // date is DateTime.MinValue
在前述範例中:
first
是null
,因為已宣告參考類型string
,但未進行指派。second
在宣告時已指派string.Empty
。 物件永遠不會有null
指派。- 儘管未獲指派,
third
仍是0
。 其為struct
(實值類型),且具有0
的default
值。 date
未初始化,但其default
值為 System.DateTime.MinValue。
從 C# 2.0 開始,您可以使用 Nullable<T>
(或速記時使用 T?
) 來定義可為 Null 的實值類型。 如此讓實值類型可為 Null。 請考慮下列 C# 程式碼片段:
int? first; // first is implicitly null (uninitialized)
int? second = null; // second is explicitly null
int? third = default; // third is null as the default value for Nullable<Int32> is null
int? fourth = new(); // fourth is 0, since new calls the nullable constructor
在前述範例中:
first
是null
,因為可為 Null 的實值型別未初始化。second
在宣告時已指派null
。third
是null
,因為Nullable<int>
的default
值是null
。fourth
是0
,因為new()
運算式會呼叫Nullable<int>
建構函式,且依預設int
是0
。
C# 8.0 引進「可為 Null 的參考類型」,您可以在其中表達參考類型「可能」是 null
,或「一律」為非 null
的意圖。 您可能會認為「我認為所有參考類型都是可為 Null 的」!您沒有說錯,實際上也是如此。 這項功能可讓您表達您的意圖,而編譯器接著會嘗試強制執行。 相同的 T?
語法表示參考類型的作用是可為 Null。
請考慮下列 C# 程式碼片段:
#nullable enable
string first = string.Empty;
string second;
string? third;
假設上述範例,編譯器會推斷您的「意圖」,如下所示:
first
「絕對不會」是null
,因為其已明確指派。second
「應該永遠不會」是null
,即使其一開始是null
。 指派值之前評估second
會導致編譯器警告,因為其未初始化。third
「可能為」null
。 例如,其「可能」會指向System.String
,但「可能」指向null
。 任一種變化都是可接受的。 如果取值third
時未先檢查其不是 Null,則編譯器會藉由發出警告來協助您。
重要
若要使用可為 Null 的參考型別功能,如上所示,則其必須位於可為 Null 的內容中。 下一節會詳細說明這一點。
可為 Null 的內容
可為 Null 的內容可針對編譯器如何解譯參考類型變數,來啟用更細緻的控制。 可為 Null 的內容有四種可能:
disable
:編譯器的行為類似於 C# 7.3 和更早版本。enable
:編譯器會啟用所有 Null 參考分析和所有語言功能。warnings
:編譯器會執行所有 Null 分析,並在程式碼可能取值null
時發出警告。annotations
:當程式碼可能取值null
時,編譯器不會執行 Null 分析或發出警告,但您仍然可以使用可為 Null 的參考類型?
和 null-forgiving 運算子 (!
) 來標註程式碼。
此課程模組的範圍限於 disable
或 enable
可為 Null 的內容。 如需詳細資訊,請參閱可為 Null 的參考類型:可為 Null 的內容。
啟用可為 Null 的參考類型
在 C# 專案檔 (.csproj) 中,將子 <Nullable>
節點新增至 <Project>
元素 (或附加至現有的 <PropertyGroup>
)。 這會將 enable
可為 Null 的內容套用至整個專案。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- Omitted for brevity -->
</Project>
或者,您可以使用編譯器指示詞,將「可為 Null 的內容」範圍設定為 C# 檔案。
#nullable enable
上述 C# 編譯器指示詞的功能相當於專案設定,但範圍限定於其所在的檔案。 如需詳細資訊,請參閱可為 Null 的參考型別:可為 Null 的內容 (文件)
重要
從 .NET 6.0 和更新版本開始的所有 C# 專案範本中,依預設會在 .csproj 檔案中啟用可為 Null 的內容。
啟用可為 Null 的內容時,您會收到新的警告。 請考慮上述 FooBar
範例,在可為 Null 的內容中進行分析時,會有兩個警告:
此
FooBar fooBar = null;
行在null
指派上有一個警告:C# 警告 CS8600:將 Null 常值或可能的 Null 值轉換為不可為 Null 的類型。該
_ = fooBar.ToString();
行也有一個警告。 這次編譯器會考慮fooBar
可能是 Null:C# 警告 CS8602:可能為 Null 參考的取值。
重要
即使您回應並消除所有警告,也無法「保證」Null 安全性。 某些受限的情節會傳遞編譯器的分析,但會導致執行階段 NullReferenceException
。
摘要
在此單元中,您已了解如何在 C# 中啟用可為 Null 的內容,以協助防範 NullReferenceException
。 在下一個單元中,您將深入了解如何在可為 Null 的內容中明確表達意圖。