小提示
本文屬於 基礎 部分,適合至少會一種程式語言並正在學習 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
當 Customer 為 null 時,Address 和 City 都不會被評估。 整個表達式會返回 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 null 且 is not null
is null與is 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仍然可能返回null。
is 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 以驗證參數檢查邏輯的測試,或者呼叫一個方法,而該方法的契約保證針對已知輸入能確保回傳值為非空。 過度使用 ! 會破壞可空參考型別的初衷。 完整說明請參見 可空參考型別。