注意
本文涵蓋可為 Null 的參考型別。 您也可以宣告可為 Null 的實值型別。
在程式碼中使用可空的參考類型,且該程式碼處於 可空的感知上下文中。 可為 Null 的參考型別、Null 靜態分析警告,以及 Null 容許運算子是選擇性的語言功能。 預設情況下全部關閉。 你可以在專案層級用建置設定控制 可空的上下文 ,或在程式碼中用 pragma 來控制。
C# 語言參考資料記錄了 C# 語言最新版本。 同時也包含即將推出語言版本公開預覽功能的初步文件。
文件中標示了語言最近三個版本或目前公開預覽版中首次引入的任何功能。
小提示
欲查詢某功能何時首次在 C# 中引入,請參閱 C# 語言版本歷史的條目。
重要
所有專案範本都會為專案啟用 可空上下文。 使用舊版範本建立的專案不包含這個元素,除非您在專案檔中啟用這些功能或使用 pragmas,否則這些功能會關閉。
在可為 Null 的感知內容中:
- 你必須初始化一個非空值的參考型態
T變數,且永遠無法指派可能是null的值。 - 你可以初始化一個參考型態
T?的變數,或用nullassignnull,但必須先與它進行null檢查,才能進行反參照。 - 當你對型態
m為 的變數T?應用 null 寬容運算子,如m!時,該變數被視為非 null。
編譯器透過上述規則強制區分非可空參考型別 T 與可空型參照型 T? 別。 類型為 T 的變數,以及類型為 T? 的變數是相同的 .NET 類型。 下列範例會宣告不可為 Null 的字串和可為 Null 的字串,然後使用 null 容許運算子將值指派給不可為 Null 的字串:
string notNull = "Hello";
string? nullable = default;
notNull = nullable!; // null forgiveness
變數和notNull兩者都nullable使用型String別。 因為不可空(non-nullable)和可空(nullable)型別都用同一個型別,所以你不能在多個位置同時使用可空的參考型別。 一般來說,你不能用可空的參考型別作為基底類別或實作介面。 你不能在任何物件建立或型別測試表達式中使用可空的參考型別。 你不能用可空的參考型別當作成員存取表達式的型別。 下列範例顯示下列建構:
public MyClass : System.Object? // not allowed
{
}
var nullEmpty = System.String?.Empty; // Not allowed
var maybeObject = new object?(); // Not allowed
try
{
if (thing is string? nullableString) // not allowed
Console.WriteLine(nullableString);
} catch (Exception? e) // Not Allowed
{
Console.WriteLine("error");
}
可為 Null 的參考和靜態分析
上一節中的範例說明可為 Null 參考型別的本質。 可為 Null 的參考型別不是新的類別型別,而是現有參考型別的註釋。 編譯器會使用這些註釋來協助您在程式碼中尋找潛在的 Null 參考錯誤。 不可為 Null 的參考型別與可為 Null 的參考型別之間沒有執行階段差異。 編譯器不會針對不可為 Null 的參考型別新增任何執行階段檢查。 優點是在編譯時期分析中。 編譯器會產生警告,協助您找出並修正程式碼中潛在的 Null 錯誤。 宣告意圖,編譯器會在程式碼違反該意圖時發出警告。
重要
可空參考註解不會帶來行為改變,但其他函式庫可能會利用反射來產生可空與不可空參考型別的不同執行時行為。 值得注意的是,Entity Framework Core 會讀取可為 Null 的屬性。 它會將可為 Null 的參考解譯為選擇值,並將不可為 Null 的參考解譯為必須值。
在可為 Null 的啟用內容中,編譯器會對任何參考型別 (可為 Null 和不可為 Null) 的變數執行靜態分析。 編譯器會將每個參考變數的 null-state 追蹤為 not-null 或 maybe-null。 不可為 Null 參考的預設狀態為 not-null。 可為 Null 參考的預設狀態為 maybe-null。
您應可放心對不可為 Null 的參考型別進行取值,因為其 null-state是 not-null。 若要強制執行該規則,如果不可為 Null 的參考型別未初始化為非 Null 值,編譯器就會發出警告。 你必須在宣告的地方指派本地變數。 必須在欄位初始設定式或每個建構函式中為每個欄位指派 not-null 值。 當將不可為 Null 的參考指派給狀態為 maybe-null 的參考時,編譯器會發出警告。 一般來說,不可空引用是 非空 的,當你取消引用這些變數時不會發出警告。
注意
如果您將 maybe-null 運算式指派給不可為 Null 的參考型別,編譯器會產生警告。 編譯器接著會產生該變數的警告,直到指派給 not-null 運算式為止。
你可以初始化或指派 null 到可空的參考類型。 因此,靜態分析必須判斷在取值之前變數是 not-null。 若判定一個可空參考為可能為 空,將其指派給不可空參考變數會產生編譯器警告。 下列類別顯示這些警告的範例:
public class ProductDescription
{
private string shortDescription;
private string? detailedDescription;
public ProductDescription() // Warning! shortDescription not initialized.
{
}
public ProductDescription(string productDescription) =>
this.shortDescription = productDescription;
public void SetDescriptions(string productDescription, string? details=null)
{
shortDescription = productDescription;
detailedDescription = details;
}
public string GetDescription()
{
if (detailedDescription.Length == 0) // Warning! dereference possible null
{
return shortDescription;
}
else
{
return $"{shortDescription}\n{detailedDescription}";
}
}
public string FullDescription()
{
if (detailedDescription == null)
{
return shortDescription;
}
else if (detailedDescription.Length > 0) // OK, detailedDescription can't be null.
{
return $"{shortDescription}\n{detailedDescription}";
}
return shortDescription;
}
}
下列程式碼片段顯示編譯器使用此類別時發出警告的位置:
string shortDescription = default; // Warning! non-nullable set to null;
var product = new ProductDescription(shortDescription); // Warning! static analysis knows shortDescription maybe null.
string description = "widget";
var item = new ProductDescription(description);
item.SetDescriptions(description, "These widgets will do everything.");
上述範例示範編譯器的靜態分析如何判斷參考變數的 null-state。 編譯器會針對 Null 檢查和指派套用語言規則,以通知其分析。 編譯器無法假設方法或屬性的語意。 如果您呼叫執行 Null 檢查的方法,編譯器不知道這些方法會影響變數的 null-state。 你可以在 API 中加入屬性,讓編譯器了解參數和回傳值的語意。 .NET 連結庫中的許多常見 API 都有這些屬性。 例如,編譯程式會將 IsNullOrEmpty 正確地解譯為空值檢查。 如需適用於 null-state 靜態分析之屬性的詳細資訊,請參閱可為 Null 屬性的文章。
可空值上下文
可空上下文決定編譯器如何處理可空參考型別註解,以及在靜態空狀態分析中產生的警告。 可空性上下文包含兩個標誌:註釋設定和警告設定。
預設情況下,現有項目的 批註 與 警告 設定都是停用的。 從 .NET 6(C# 10)開始,new專案預設會啟用這兩個旗標。 可為 Null 環境設定兩個不同旗標的原因在於,使遷移在引入可為 Null 參考型別之前的現有大型專案更為容易。
針對小型專案,您可以啟用可為 Null 的參考型別、修正警告並繼續。 然而,對於較大的專案和多專案解決方案,這個過程可能會產生大量警告。 您可以逐檔案使用 pragmas 來啟用可為 Null 的參考型別,當您開始使用這些型別時。 在現有的程式碼基底中開啟防止擲回 System.NullReferenceException 的新功能時,可能會發生干擾:
- 所有明確定義型別的參考變數都會被視為不可為 Null 的參考型別。
- 泛型中
class條件約束的意義已變更為表示不可為 Null 的參考型別。 - 由於有這些新規則,因此產生新的警告。
可為 Null 註釋的環境決定編譯器的行為。 可為 Null 的上下文 設定有四種可能的組合:
-
雙方皆停用:程式碼忽略可空性。
停用 與啟用可空參考型別之前的行為相符,但新語法將產生警告而非錯誤。
- 系統會停用可為 Null 的警告。
- 所有參考型別變數都是可為 Null 的參考型別。
- 使用
?尾碼來宣告可空參照型別會產生警告。 - 您可以使用 null 容許運算子
!,但沒有任何作用。
-
同時啟用:編譯器會啟用所有 null 參考分析和所有語言功能。
- 所有新的可為 Null 的警告都已啟用。
- 您可以使用
?尾碼來宣告可空的參考型別。 - 沒有
?尾碼的參考型別變數是非空引用型別。 - 消除空值檢查運算子會抑制可能解引用
null的警告。
- 啟用 警告:編譯程式會執行所有空值分析,並在程式碼可能解參考
null時發出警告。- 所有新的可為 Null 的警告都已啟用。
- 使用
?尾碼來宣告可空參照型別會產生警告。 - 所有參考型別變數都允許為 Null。 不過,除非使用 尾碼宣告,否則成員在所有方法起始大括弧時的空值狀態均為
?。 - 您可以使用 null 释疑运算子
!。
-
註解啟用:編譯器在程式碼可能解參考
null,或將可能為 null 的表達式指派給不可為 Null 的變數時,不會發出警告。- 系統會停用所有新的可為 Null 警告。
- 您可以使用
?尾碼來宣告可空的參考型別。 - 沒有
?尾碼的參考型別變數是非空引用型別。 - 您可以使用 null 容許運算子
!,但沒有任何作用。
你可以用 <Nullable>.csproj 檔案中的元素設定專案的可空標註上下文和可空警告上下文。 此元素配置編譯器如何解讀型別的空性及其發出的警告。 下表顯示允許的值,並摘要說明它們所指定的內容。
| 背景 | 取消參考警告 | 任務分配警告 | 參考類型 |
? 尾碼 |
! 運算子 |
|---|---|---|---|---|---|
disable |
Disabled | Disabled | 全部都是可為空的 | 產生警告 | 沒有作用 |
enable |
已啟用 | 已啟用 | 除非用?宣告,否則不可為 Null。 |
宣告可為空值型別 | 針對可能的 null 賦值隱藏警告 |
warnings |
已啟用 | 不適用 | 所有皆可為 Null,但在方法開頭的左大括弧處,成員會被視作 「非 Null」。 | 產生警告 | 針對可能的 null 賦值隱藏警告 |
annotations |
Disabled | Disabled | 除非用?宣告,否則不可為 Null。 |
宣告可為空值型別 | 沒有作用 |
在 已停用 的內容中編譯的程式碼裡,參考型別變數是 可空性忽略 的。 您可以將 null 常值或 「可能為 Null」 變數指派給 「忽略可空性」 的變數。 不過,null 忽略性變數的預設狀態為非 null。
選擇最適合你專案的環境:
- 針對您不想依據診斷或新功能來更新的舊版專案,請選擇 [停用]。
- 選擇 [警告] 來判斷程式碼可能擲回 System.NullReferenceException 的位置。 您可以在修改程式碼之前先處理這些警告,以啟用不可為 Null 的參考型別。
- 在啟用警告之前,選擇 [註釋] 來表達您的設計意圖。
- 選擇 [啟用] 以保護您想防止空值參考例外的新專案和使用中專案。
範例:
<Nullable>enable</Nullable>
您也可以在原始程式碼中的任何位置使用指令來設定相同的標誌。 當您移轉大型程式碼基底時,這些指令是最有用的。
-
#nullable enable:將批註和警告旗標設定為 啟用。 -
#nullable disable:將批註和警告旗標設定為 停用。 -
#nullable restore:將批註旗標和警告旗標還原至項目設定。 -
#nullable disable warnings:會將警告旗設為 停用。 -
#nullable enable warnings:會將警告旗標設為 啟用。 -
#nullable restore warnings:將警告旗標還原至項目設定。 -
#nullable disable annotations: 將註解標記設為 停用。 -
#nullable enable annotations:將註解標記設 為啟用。 -
#nullable restore annotations:將批註旗標還原至項目設定。
針對任何程式碼,您可以設定下列任何組合:
| 警告旗標 | 註釋標記 | 使用 |
|---|---|---|
| 專案預設值 | 專案預設值 | 預設 |
| 啟用 | 停用 | 修正分析警告 |
| 啟用 | 專案預設值 | 修正分析警告 |
| 專案預設值 | 啟用 | 新增型別註釋 |
| 啟用 | 啟用 | 程式碼已移轉 |
| 停用 | 啟用 | 修正警告之前標註程式碼 |
| 停用 | 停用 | 將舊版程式碼新增至移轉的專案 |
| 專案預設值 | 停用 | 很少 |
| 停用 | 專案預設值 | 很少 |
這九組組合讓你能細緻控制編譯器對程式碼所發出的診斷結果。 您可以在您要更新的任何區域中啟用更多功能,而不會看到尚未準備好要解決的其他警告。
重要
全域可空的上下文不適用於產生的程式碼檔案。 不論採用任何一個策略,所有標記為產生的來源檔案,會停用可為 null 的上下文。 這個條件表示編譯器不會在產生的檔案中註解任何 API。 編譯器不會對產生的檔案產生可空除的警告。 檔案會以以下四種方式之一標記為產生:
- 在 .editorconfig 中,於套用至該檔案的區段中,指定
generated_code = true。 - 將
<auto-generated>或<auto-generated/>置於檔案頂端的註解中。 它可以在註解的任一行,但註解區塊必須是檔案中的第一個元素。 - 使用 TemporaryGeneratedFile_ 做為檔案名稱的開頭
- 使用 .designer.cs、.generated.cs、.g.cs 或 .g.i.cs 做為檔案名稱的結尾。
發電機可透過預 #nullable 處理器指令選擇加入。
根據預設,停用可為 Null 的註釋和警告旗標 <Nullable>enable</Nullable> 元素,並將這些旗標設為 enabled。
這些選項提供兩種不同的策略,讓您可以更新現有的程式碼基底,以使用可為 Null 的參考型別。
設定可為 Null 的內容
你可以用兩種方式控制可空化的上下文。 在專案層級,加入 <Nullable>enable</Nullable> 專案設定。 在單一 C# 原始碼檔案中,加入 #nullable enable pragma 以啟用可空上下文。 欲了解更多資訊,請參閱 設定可空策略。 在 .NET 6 之前,新專案會使用預設值,<Nullable>disable</Nullable>。 從 .NET 6 開始,新專案會在專案檔中包含 <Nullable>enable</Nullable> 元素。
泛型
當你使用型別參數 T,作為其可空對應的對應物 T?時,實際型別參數決定了如何解釋。? 請考慮以下通用聲明:
public class Box<T>
{
public T Contents { get; set; }
}
由於型別參數可以代表參考型別或值型別,因此的 T? 意義取決於呼叫者所提供的型別參數。 以下規則描述了當 T 無約束時 解決為 的條件T?:
- 型別參數是一個不可空的參考型別。 對於
Box<string>,T是string,且T?是string?對應的可空參考型態。 - 型別參數是一個值型態。 對於
Box<int>,T且intT?也是int—— 相同的值型態。 註解對值型別沒有影響,除非型別參數有struct約束,T?此時 表示 Nullable<T> (int?)。 - 型別參數已經是可空的。 對於
Box<string?>,T且string?T?仍string?為 。 你不會有「雙重可取消」的類型。
限制 限制允許的參數類型。 它們也讓編譯器推理如何使用 T :
-
where T : class需要一個不可空的參考型別。Box<string>是被允許的;Box<string?>會產生警告。 -
where T : class?允許可空或不可空的參考型別。 兩者皆Box<string>Box<string?>可。 -
where T : struct需要一個不可空的值型態。Box<int>是被允許的;Box<int?>不是。 在此限制下,T?在一般意義 Nullable<T>-中,對於Box<int>,T?為 。int? -
where T : notnull需要一個不可空的參考或值型別。Box<string>Box<int>並且被允許;Box<string?>產生警告。 -
where T : BaseType需要一個不可空的參考型別,且該型別由BaseType衍生出。 附加?(where T : BaseType?)以允許可空的導出型別。
這些約束幫助編譯器推理一般型態參數的使用方式:
public static T? FirstOrDefault<T>(IEnumerable<T> source)
{
foreach (T item in source)
{
return item;
}
return default;
}
public static void RequireNotNull<T>(T value) where T : notnull
{
ArgumentNullException.ThrowIfNull(value);
}
public static void Generics()
{
string? first = FirstOrDefault<string>([]);
Console.WriteLine(first ?? "<empty>");
RequireNotNull("not null");
}
C# 語言規格
欲了解更多資訊,請參閱 C# 語言規範中的可空參考型別章節。