空傳播
?.
運算子和 ?[...]
運算子稱為空條件運算子 。它有時也被其他名稱引用,例如安全導航運算子 。
這很有用,因為如果將 .
(成員訪問器)運算子應用於計算為 null
的表示式,程式將丟擲 NullReferenceException
。如果開發人員使用 ?.
(null-conditional)運算子,則表示式將計算為 null 而不是丟擲異常。
請注意,如果使用 ?.
運算子且表示式為非 null,則 ?.
和 .
是等效的。
基本
var teacherName = classroom.GetTeacher().Name;
// throws NullReferenceException if GetTeacher() returns null
如果 classroom
沒有老師,GetTeacher()
可能會返回 null
。如果是 null
並且訪問了 Name
屬性,則會丟擲 NullReferenceException
。
如果我們修改此語句以使用 ?.
語法,則整個表示式的結果將為 null
:
var teacherName = classroom.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null
隨後,如果 classroom
也可能是 null
,我們也可以將此宣告寫成:
var teacherName = classroom?.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null OR classroom is null
這是一個短路示例:當使用空條件運算子的任何條件訪問操作求值為 null 時,整個表示式立即求值為 null,而不處理鏈的其餘部分。
當包含空條件運算子的表示式的終端成員是值型別時,表示式求值為該型別的 Nullable<T>
,因此不能用作沒有 ?.
的表示式的直接替換。
bool hasCertification = classroom.GetTeacher().HasCertification;
// compiles without error but may throw a NullReferenceException at runtime
bool hasCertification = classroom?.GetTeacher()?.HasCertification;
// compile time error: implicit conversion from bool? to bool not allowed
bool? hasCertification = classroom?.GetTeacher()?.HasCertification;
// works just fine, hasCertification will be null if any part of the chain is null
bool hasCertification = classroom?.GetTeacher()?.HasCertification.GetValueOrDefault();
// must extract value from nullable to assign to a value type variable
與 Null-Coalescing 運算子一起使用(??)
如果表示式解析為 null
,則可以將空條件運算子與 Null- coilecing Operator (??
)組合以返回預設值。使用上面的示例:
var teacherName = classroom?.GetTeacher()?.Name ?? "No Name";
// teacherName will be "No Name" when GetTeacher()
// returns null OR classroom is null OR Name is null
與索引器一起使用
null 條件運算子可以與索引器一起使用 :
var firstStudentName = classroom?.Students?[0]?.Name;
在上面的例子中:
- 第一個
?.
確保classroom
不是null
。 - 第二個
?
確保整個Students
系列不是null
。 - 索引器之後的第三個
?.
確保[0]
索引器沒有返回null
物件。應該注意的是,這個操作仍然可以投擲一個IndexOutOfRangeException
。
與 void 函式一起使用
Null 條件運算子也可以與 void
函式一起使用。但是在這種情況下,該宣告不會評估為 null
。它只會阻止一個人。
List<string> list = null;
list?.Add("hi"); // Does not evaluate to null
與事件呼叫一起使用
假設以下事件定義:
private event EventArgs OnCompleted;
在呼叫事件時,傳統上,最好在沒有訂閱者的情況下檢查事件是否為 null
:
var handler = OnCompleted;
if (handler != null)
{
handler(EventArgs.Empty);
}
由於引入了空條件運算子,因此可以將呼叫簡化為單行:
OnCompleted?.Invoke(EventArgs.Empty);
限制
Null 條件運算子產生 rvalue,而不是 lvalue,也就是說,它不能用於屬性賦值,事件訂閱等。例如,以下程式碼將不起作用:
// Error: The left-hand side of an assignment must be a variable, property or indexer
Process.GetProcessById(1337)?.EnableRaisingEvents = true;
// Error: The event can only appear on the left hand side of += or -=
Process.GetProcessById(1337)?.Exited += OnProcessExited;
陷阱
注意:
int? nameLength = person?.Name.Length; // safe if 'person' is null
是不一樣的:
int? nameLength = (person?.Name).Length; // avoid this
因為前者對應於:
int? nameLength = person != null ? (int?)person.Name.Length : null;
而後者對應於:
int? nameLength = (person != null ? person.Name : null).Length;
儘管此處使用三元運算子 ?:
來解釋兩種情況之間的差異,但這些運算子並不等效。通過以下示例可以輕鬆演示這一點:
void Main()
{
var foo = new Foo();
Console.WriteLine("Null propagation");
Console.WriteLine(foo.Bar?.Length);
Console.WriteLine("Ternary");
Console.WriteLine(foo.Bar != null ? foo.Bar.Length : (int?)null);
}
class Foo
{
public string Bar
{
get
{
Console.WriteLine("I was read");
return string.Empty;
}
}
}
哪個輸出:
空傳播
我讀了
0
三元
我讀了
我讀了
0
為避免多次呼叫,等效的是:
var interimResult = foo.Bar;
Console.WriteLine(interimResult != null ? interimResult.Length : (int?)null);
這種差異在某種程度上解釋了為什麼表示式樹中尚不支援空傳播運算子。