C# 空運算元

小提示

本文屬於 基礎 部分,適合至少會一種程式語言並正在學習 C# 的開發者。 如果你是程式新手,建議先從 入門 教學開始。 完整運算子參考,請參閱語言參考中的 成員存取運算子空合併運算子

C# 提供了多種運算子,使 null-safe 程式碼變得簡潔。 這些運算子讓你可以在單一表達式中表達空安全存取、備援值,以及空值測試,而不需在程式碼中嵌套if (x != null)防護機制。

本文涵蓋 ?.?[] 的 Null條件取用、?? 的 Null合併、??= 的 Null合併指派,以及 is null/is not null 的 Null樣式匹配。

空條件成員存取 ?.

運算子 ?. 僅在物件非空時存取成員。 當物件為 null時,整個表達式的值為 null ,而非拋 NullReferenceException出 :

string? name = null;

// Without ?., accessing a member on null throws NullReferenceException:
// int len = name.Length; // throws if name is null

// ?. returns null instead of throwing:
int? len = name?.Length;
Console.WriteLine(len.HasValue); // False

name = "C#";
Console.WriteLine(name?.Length); // 2

?.操作員短路:當左側為 null時,右側所有區域都會被跳過。 不會執行方法呼叫,也不會產生副作用。

你可以在一個表達式中串連多個 ?. 運算子。 序列在遇到第一個 null 時停止:

string? input = null;

// Chain ?. across multiple method calls — short-circuits at the first null:
string? upper = input?.Trim()?.ToUpperInvariant();
Console.WriteLine(upper ?? "(none)"); // (none)

input = "  hello  ";
Console.WriteLine(input?.Trim()?.ToUpperInvariant()); // HELLO

空條件索引器存取 ?[]

操作員 ?[] 對索引器與陣列存取也套用相同的短路行為。 當該集合本身可能有 null

string[]? tags = null;

// ?[] accesses an element only when the collection is non-null
string? first = tags?[0];
Console.WriteLine(first ?? "(none)"); // (none)

tags = ["csharp", "dotnet", "nullable"];
Console.WriteLine(tags?[0]);          // csharp

鏈狀零條件運算子

串接多個 ?. 運算子以遍歷可能為空的參考的路徑。 鏈條在第一個 null短路:

var order = new Order("ORD-001", null);

// Each ?. short-circuits when null: Customer is null, so Address and City are never accessed
string? city = order.Customer?.Address?.City;
Console.WriteLine(city ?? "(no city)"); // (no city)

var fullOrder = new Order("ORD-002",
    new Customer("Alice", new Address("123 Main St", "Springfield", "IL")));

Console.WriteLine(fullOrder.Customer?.Address?.City); // Springfield

Customernull 時,AddressCity 都不會被評估。 整個表達式會返回 null

安全線程委派調用

?. 提供一種乾淨且執行緒安全的呼叫代理或事件發起的方式。 代理式只被評估一次,因此在空檢查與呼叫之間,沒有其他執行緒可以取消訂閱的視窗:

EventHandler? clicked = null;

// No subscribers — ?.Invoke does nothing instead of throwing NullReferenceException
clicked?.Invoke(null, EventArgs.Empty);

clicked += (_, _) => Console.WriteLine("Button clicked!");

// With a subscriber — ?.Invoke calls the handler
clicked?.Invoke(null, EventArgs.Empty);
// Output: Button clicked!

此模式取代了舊的 if (clicked != null) clicked(...) 範式。

零聚合 ??

?? 運算子在左運算元為非空值時返回左運算元,在左運算元為 null 時返回右運算元。 用它來提供預設值:

string? username = null;

// ?? returns the right-hand value when the left-hand is null
string display = username ?? "Guest";
Console.WriteLine(display); // Guest

username = "alice";
display  = username ?? "Guest";
Console.WriteLine(display); // alice

?? 是右結合的,因此 a ?? b ?? c 當值為 a ?? (b ?? c)。 第一個非空值獲勝。 常見的模式是將 ?.?? 串接起來:使用 ?. 來安全地遍歷可能為空的鏈,然後若鏈返回 ??,則使用 null 來替換為預設值。 完整範例請參見 Combine null operators

零聚合指派 ??=

??=運算子只有在變數為 null時才會將右手值指派給變數。 可以用它來進行延遲初始化:

List<string>? cache = null;

// ??= assigns only when the variable is null
cache ??= LoadData();
Console.WriteLine(cache.Count); // 3

// cache is already non-null, so LoadData() isn't called again
cache ??= LoadData();
Console.WriteLine(cache.Count); // 3

static List<string> LoadData() => ["alpha", "beta", "gamma"];

右側的表達式僅在變數為 null時才被評估。 當變數已經有值時,右側根本不會被評估。

空條件指派(C# 14)

從 C# 14 開始,你可以使用 ?.?[] 作為作業目標。 當左側物件非空時,該指派才會執行:

AppConfig? config = new AppConfig();

// Assigns only when config is non-null (C# 14)
config?.Theme = "dark";
Console.WriteLine(config?.Theme); // dark

AppConfig? missing = null;
missing?.Theme = "light";                         // no-op: missing is null
Console.WriteLine(missing?.Theme ?? "(no config)"); // (no config)

只有當左邊已知非空時,才會評估右邊的值。

空模式匹配: is nullis not null

is nullis not null模式測試表達式是否為null

string? input = null;

// is null is the preferred test — unaffected by operator overloading
if (input is null)
{
    Console.WriteLine("No input provided.");
}

// == null also works, but a custom == operator can change its behavior
if (input == null)
{
    Console.WriteLine("Still no input.");
}

偏好 is null 比起 == null 來進行空值檢查。 ==運算子可以被重載,也就是說,當x == null不是true時,如果類型定義了自訂的等於運算子,x仍然可能返回nullis null模式總是測試實際的空參考,不論運算子是否超載。

string? value = "hello";

if (value is not null)
{
    Console.WriteLine(value.ToUpper()); // HELLO
}

合併空算子

實務上,你經常會將多個這類運算子結合使用。 一個表達式可以安全地遍歷深物件圖,套用備援,然後對結果進行守護:

Order? order = GetPendingOrder();

// Chain ?. for safe traversal, ?? for a fallback, is null for a clear guard
string city = order?.Customer?.Address?.City ?? "unknown";

if (order is null)
{
    Console.WriteLine("No pending order.");
}
else
{
    Console.WriteLine($"Shipping to: {city}");
}
// Output: No pending order.

零寬容算子 !

!後綴運算子會抑制可取消的警告。 附加 ! 以告訴編譯器「此表達式絕對不是空的」。運算子在執行時不會產生影響。 它只影響編譯器的虛無狀態分析。

string? name = FindUser("alice");

// Use ! only when you have information the compiler doesn't.
// FindUser guarantees a non-null result for known usernames.
int length = name!.Length;
Console.WriteLine(length); // 5

請節制使用 ! ,且只有在你掌握編譯器沒有的資訊時才使用。 例如,故意傳遞 null 以驗證參數檢查邏輯的測試,或者呼叫一個方法,而該方法的契約保證針對已知輸入能確保回傳值為非空。 過度使用 ! 會破壞可空參考型別的初衷。 完整說明請參見 可空參考型別

參見