表達意圖
在上一個單元中,您已了解 C# 編譯器如何執行靜態分析,以協助防範 NullReferenceException
。 您也已了解如何啟用可為 Null 的內容。 在此單元中,您將深入了解如何在可為 Null 的內容中明確表達意圖。
宣告變數
啟用可為 Null 的內容後,您可以進一步了解編譯器如何觀察您的程式碼。 您可以根據已啟用 Null 內容產生的警告採取行動,而且這麼做會明確定義您的意圖。 例如,讓我們繼續檢查 FooBar
程式碼,並檢查宣告和指派:
// Define as nullable
FooBar? fooBar = null;
請注意 ?
已新增至 FooBar
。 這會告知編譯器您明確希望 fooBar
可為 Null。 如果您不想讓 fooBar
可為 Null,但仍想要避免警告,請考慮下列事項:
// Define as non-nullable, but tell compiler to ignore warning
// Same as FooBar fooBar = default!;
FooBar fooBar = null!;
本範例會將 null-forgiving (!
) 運算子新增至 null
,指示編譯器將這個變數明確初始化為 null。 編譯器將不會發出有關此參考為 Null 的警告。
最好是在宣告不可為 Null 的變數時,指派非 Null 變數非 null
值 (如果可能):
// Define as non-nullable, assign using 'new' keyword
FooBar fooBar = new(Id: 1, Name: "Foo");
操作員
如上一單元中所述,C# 會定義數個運算子,以在可為 Null 的參考型別周圍表達您的意圖。
null-forgiving (!
) 運算子
您已在上一節中了解 null-forgiving 運算子 (!
)。 該運算子會告知編譯器忽略 CS8600 警告。 這是告知編譯器您知道自己在做什麼的其中一種方式,但有一點需要注意,您必須真的知道自己在做什麼!
當您在初始化不可為 Null 的類型,並已啟用可為 Null 的內容時,可能需要明確要求編譯器進行 forgiveness。 例如,請考慮下列程式碼:
#nullable enable
using System.Collections.Generic;
var fooList = new List<FooBar>
{
new(Id: 1, Name: "Foo"),
new(Id: 2, Name: "Bar")
};
FooBar fooBar = fooList.Find(f => f.Name == "Bar");
// The FooBar type definition for example.
record FooBar(int Id, string Name);
在上述範例中,FooBar fooBar = fooList.Find(f => f.Name == "Bar");
會產生 CS8600 警告,因為 Find
可能會傳回 null
。 這個可能的 null
會指派給 fooBar
,此內容不可為 Null。 不過,在此嘗試的範例中,我們知道 Find
永遠不會以寫入方式傳回 null
。 您可以使用 null-forgiving 運算子向編譯器表達此意圖:
FooBar fooBar = fooList.Find(f => f.Name == "Bar")!;
請注意 !
位於 fooList.Find(f => f.Name == "Bar")
的結尾處。 這會告知編譯器您知道 Find
方法傳回的物件可能是 null
,而且沒有問題。
在方法呼叫或屬性評估之前,也可以將 null-forgiving 運算子套用至内嵌物件。 考慮其他嘗試的範例:
List<FooBar>? fooList = FooListFactory.GetFooList();
// Declare variable and assign it as null.
FooBar fooBar = fooList.Find(f => f.Name == "Bar")!; // generates warning
static class FooListFactory
{
public static List<FooBar>? GetFooList() =>
new List<FooBar>
{
new(Id: 1, Name: "Foo"),
new(Id: 2, Name: "Bar")
};
}
// The FooBar type definition for example.
record FooBar(int Id, string Name);
在前述範例中:
GetFooList
是靜態方法,會傳回可為 Null 的型別。List<FooBar>?
fooList
會獲派GetFooList
所傳回的值。- 編譯器會在
fooList.Find(f => f.Name == "Bar");
上產生警告,因為指派給fooList
的值可能是null
。 - 假設
fooList
不是null
,Find
可能會傳回null
,但我們知道不會傳回,因此會套用 null-forgiving 運算子。
您可以將 null-forgiving 運算子套用至 fooList
以停用警告:
FooBar fooBar = fooList!.Find(f => f.Name == "Bar")!;
注意
您應該謹慎地使用 Null 表示運算子。 若使用其關閉警告,表示您告知編譯器不需協助您探索可能的 Null 錯誤。 請謹慎使用,並只有在您確定時才使用。
如需詳細資訊,請參閱 ! (null-forgiving) 運算子 (C# 參考)。
Null 聯合 (??
) 運算子
使用可為 Null 的型別時,您可能需要評估其目前是否為 null
,並採取特定動作。 例如,當可為 Null 的類型已指派為 null
或未初始化時,您可能需要指派非 Null 值。 這就是 Null 聯合運算子 (??
) 有用之處。
請考慮下列範例:
public void CalculateSalesTax(IStateSalesTax? salesTax = null)
{
salesTax ??= DefaultStateSalesTax.Value;
// Safely use salesTax object.
}
在上述 C# 程式碼中:
- 參數
salesTax
定義為可為 Null 的IStateSalesTax
。 - 在方法主體中,
salesTax
會使用 Null 聯合運算子有條件地指派。- 這可確保當
salesTax
作為null
傳入時會有值。
- 這可確保當
提示
這項功能相當於下列 C# 程式碼:
public void CalculateSalesTax(IStateSalesTax? salesTax = null)
{
if (salesTax is null)
{
salesTax = DefaultStateSalesTax.Value;
}
// Safely use salesTax object.
}
以下是另一個常見 C# 慣用語的範例,其中 Null 聯合運算子可能很有用:
public sealed class Wrapper<T> where T : new()
{
private T _source;
// If given a source, wrap it. Otherwise, wrap a new source:
public Wrapper(T source = null) => _source = source ?? new T();
}
在上述 C# 程式碼中:
- 定義泛型包裝函式類別,其中泛型型別參數受限於
new()
。 - 建構函式接受預設為
null
的參數T source
。 - 包裝的
_source
會有條件地初始化為new T()
。
如需詳細資訊,請參閱 ?? 和 ??= 運算子 (C# 參考)。
Null 條件運算子 (?.
)
使用可為 Null 的型別時,您可能需要根據 null
物件的狀態有條件地執行動作。 例如,在上一個單元中,FooBar
記錄是用來藉由取值 null
來示範 NullReferenceException
。 這是在呼叫其 ToString
時所造成。 請考慮這個相同的範例,但現在套用 Null 條件運算子:
using System;
// Declare variable and assign it as null.
FooBar fooBar = null;
// Conditionally dereference variable.
var str = fooBar?.ToString();
Console.Write(str);
// The FooBar type definition.
record FooBar(int Id, string Name);
在上述 C# 程式碼中:
- 有條件地取值
fooBar
,並將ToString
的結果指派給str
變數。- 變數
str
的類型string?
(可為 Null 的字串)。
- 變數
- 其會將
str
的值寫入標準輸出中,但不會有任何內容。 - 呼叫
Console.Write(null)
是有效的,因此沒有警告。 - 如果您要呼叫
Console.Write(str.Length)
,則可能會收到警告,因為您可能會取值 null。
提示
這項功能相當於下列 C# 程式碼:
using System;
// Declare variable and assign it as null.
FooBar fooBar = null;
// Conditionally dereference variable.
string str = (fooBar is not null) ? fooBar.ToString() : default;
Console.Write(str);
// The FooBar type definition.
record FooBar(int Id, string Name);
您可以結合運算子來進一步表達您的意圖。 例如,您可以鏈結 ?.
和 ??
運算子:
FooBar fooBar = null;
var str = fooBar?.ToString() ?? "unknown";
Console.Write(str); // output: unknown
如需詳細資訊,請參閱 ?. 和 ?[] (null 條件式) 運算子。
摘要
在本單元中,您已了解如何在程式碼中表達 Null 屬性意圖。 在下一個單元中,您會將學到的內容套用至現有的專案。