成員存取運算子和運算式 - 點運算子、索引子和叫用運算子。
您可以使用數個運算子和運算式來存取一個類型成員。 這些運算子包括成員存取 (.
)、陣列元素或索引子存取 ([]
)、index-from-end (^
)、範圍 (..
)、Null 條件運算子 (?.
和 ?[]
),以及方法叫用 (()
)。 其中包括 Null 條件成員存取 (?.
),以及索引子存取 (?[]
) 運算子。
.
(成員存取):可存取命名空間或類型的成員[]
(陣列項目或索引子存取):可存取陣列項目或類型索引子?.
和?[]
(Null 條件運算子):只有在運算元為非 Null 時,才可執行成員或項目存取作業()
(引動流程):可呼叫存取的方法或叫用委派^
index-from-end:表示項目位置來自序列結尾..
(範圍):指定可用來取得序列項目範圍的索引範圍
成員存取運算式 .
您會使用 .
語彙基元來存取命名空間或類型的成員,如下列範例所示:
- 使用
.
來存取命名空間內的巢狀命名空間,如下列using
指示詞的範例所示:
using System.Collections.Generic;
- 使用
.
來形成「限定名稱」以存取命名空間內的類型,如下列程式碼所示:
System.Collections.Generic.IEnumerable<int> numbers = [1, 2, 3];
使用 using
指示詞來使限定名稱的使用為選擇性。
- 使用
.
來存取類型成員 (靜態及非靜態),如下列程式碼所示:
List<double> constants =
[
Math.PI,
Math.E
];
Console.WriteLine($"{constants.Count} values to show:");
Console.WriteLine(string.Join(", ", constants));
// Output:
// 2 values to show:
// 3.14159265358979, 2.71828182845905
您也可以使用 .
來存取擴充方法。
索引子運算子 []
中括弧 ([]
) 通常用於陣列、索引子或指標元素存取。 從 C# 12 開始,[]
會將集合運算式括起來。
陣列存取
以下範例將示範如何存取陣列元素:
int[] fib = new int[10];
fib[0] = fib[1] = 1;
for (int i = 2; i < fib.Length; i++)
{
fib[i] = fib[i - 1] + fib[i - 2];
}
Console.WriteLine(fib[fib.Length - 1]); // output: 55
double[,] matrix = new double[2,2];
matrix[0,0] = 1.0;
matrix[0,1] = 2.0;
matrix[1,0] = matrix[1,1] = 3.0;
var determinant = matrix[0,0] * matrix[1,1] - matrix[1,0] * matrix[0,1];
Console.WriteLine(determinant); // output: -3
如果陣列索引超出陣列的對應維度界限,就會擲回 IndexOutOfRangeException。
如上述範例所示,您也會在宣告陣列類型或將陣列執行個體具現化時使用方括弧。
如需陣列的詳細資訊,請參閱陣列。
索引子存取
下列範例使用 .NETDictionary<TKey,TValue> 類型示範索引子存取:
var dict = new Dictionary<string, double>();
dict["one"] = 1;
dict["pi"] = Math.PI;
Console.WriteLine(dict["one"] + dict["pi"]); // output: 4.14159265358979
索引子可讓您透過與陣列編製索引類似的方式,為使用者定義型別的執行個體編製索引。 與必須是整數的陣列索引不同,索引子參數可以宣告為任何類型。
如需索引子的詳細資訊,請參閱索引子。
[] 的其他用法
如需有關指標元素存取的相關資訊,請參閱指標相關運算子一文的指標元素存取運算子 [] 一節。 如需集合運算式的相關資料,請參閱集合表達式一文。
您也可以使用中括弧來指定屬性:
[System.Diagnostics.Conditional("DEBUG")]
void TraceMethod() {}
Null 條件運算子 ?.
和 ?[]
Null 條件運算子只有在其運算元評估為非 Null 時,才會將成員存取 (?.
),或項目存取 (?[]
)、作業套用至該運算元,否則會返回 null
。 換句話說:
如果
a
評估為null
,則a?.x
或a?[x]
的結果為null
。如果
a
評估為非 Null,則a?.x
或a?[x]
的結果會分別與a.x
或a[x]
的結果相同。注意
如果
a.x
或a[x]
擲回例外狀況,a?.x
或a?[x]
會針對非 Nulla
擲回相同的例外狀況。 例如,如果a
是非 Null 陣列執行個體,而且x
超出 的a
界限,則a?[x]
會擲回 IndexOutOfRangeException。
Null 條件運算子會執行最少運算。 換句話說,如果條件式成員或項目存取作業鏈結中的一個作業傳回 null
,則鏈結的其餘部分不會執行。 在下列範例中,如果 A
評估為 null
,則不會評估 B
;如果 A
或 B
評估為 null
,則不會評估 C
:
A?.B?.Do(C);
A?.B?[C];
如果 A
可能是 Null,但如果 A 不是 Null,則 B
和 C
不會是 Null,您只需要將 Null 條件運算子套用至 A
:
A?.B.C();
在上述範例中,如果 A
為 Null,B
不會評估,也不會呼叫 C()
。 不過,如果鏈結的成員存取中斷,例如,括弧如 (A?.B).C()
,則不會發生最短路徑。
下列範例示範 ?.
和 ?[]
運算子的用法:
double SumNumbers(List<double[]> setsOfNumbers, int indexOfSetToSum)
{
return setsOfNumbers?[indexOfSetToSum]?.Sum() ?? double.NaN;
}
var sum1 = SumNumbers(null, 0);
Console.WriteLine(sum1); // output: NaN
List<double[]?> numberSets =
[
[1.0, 2.0, 3.0],
null
];
var sum2 = SumNumbers(numberSets, 0);
Console.WriteLine(sum2); // output: 6
var sum3 = SumNumbers(numberSets, 1);
Console.WriteLine(sum3); // output: NaN
namespace MemberAccessOperators2;
public static class NullConditionalShortCircuiting
{
public static void Main()
{
Person? person = null;
person?.Name.Write(); // no output: Write() is not called due to short-circuit.
try
{
(person?.Name).Write();
}
catch (NullReferenceException)
{
Console.WriteLine("NullReferenceException");
}; // output: NullReferenceException
}
}
public class Person
{
public required FullName Name { get; set; }
}
public class FullName
{
public required string FirstName { get; set; }
public required string LastName { get; set; }
public void Write() => Console.WriteLine($"{FirstName} {LastName}");
}
上述兩個範例中的第一個範例也會使用 Null 聯合運算子 ??
來指定替代運算式,以在 Null 條件運算的結果為 null
時進行評估。
如果 a.x
或 a[x]
屬於不可為 Null 的實值型別 T
,a?.x
或 a?[x]
為對應的可為 Null 的實值型別 T?
。 如果您需要 T
類型的運算式,請將 Null 聯合運算子 ??
套用至 Null 條件運算式,如下列範例所示:
int GetSumOfFirstTwoOrDefault(int[]? numbers)
{
if ((numbers?.Length ?? 0) < 2)
{
return 0;
}
return numbers[0] + numbers[1];
}
Console.WriteLine(GetSumOfFirstTwoOrDefault(null)); // output: 0
Console.WriteLine(GetSumOfFirstTwoOrDefault([])); // output: 0
Console.WriteLine(GetSumOfFirstTwoOrDefault([3, 4, 5])); // output: 7
在上述範例中,如果您未使用 ??
運算子,當 numbers
為 null
時 numbers?.Length < 2
則評估為 false
。
注意
?.
運算子會評估其左側運算元不超過一次,保證在驗證為非 Null 之後無法將其變更為 null
。
Null 條件成員存取運算子 ?.
也被稱為 Elvis 運算子。
安全執行緒委派引動流程
使用 ?.
運算子來檢查委派是否為非 Null,然後以安全執行緒方式叫用它 (例如當您引發事件時),如下列程式碼所示:
PropertyChanged?.Invoke(…)
該程式碼相當於下列程式碼:
var handler = this.PropertyChanged;
if (handler != null)
{
handler(…);
}
上述範例是安全執行緒的方式,可確保只叫用非 Null handler
。 因為委派執行個體是不可變的,所以沒有任何執行緒可以變更 handler
區域變數所參考的物件。 特別是,如果另一個執行緒執行的程式碼會取消 PropertyChanged
訂閱事件,而在 handler
叫用之前 PropertyChanged
變成 null
,則 handler
所參考的物件仍不會受到影響。
叫用流程運算式 ()
下列範例示範如何呼叫方法 (使用或不使用引數),以及叫用委派:
Action<int> display = s => Console.WriteLine(s);
List<int> numbers =
[
10,
17
];
display(numbers.Count); // output: 2
numbers.Clear();
display(numbers.Count); // output: 0
() 的其他用法
您也可以使用括弧來調整評估運算式中作業的順序。 如需詳細資訊,請參閱 C# 運算子。
Cast 運算式 \(其能執行明確類型轉換\) 也會使用括號。
index-from-end 運算子 ^
索引和範圍運算子可以搭配可計算的類型使用。 可計算類型是具有 int
屬性的名為 Count
或 Length
的類型,具有可存取的 get
存取子。 集合物件運算式也依賴可計算類型。
^
運算子表示序列結尾的項目位置。 若為長度 length
的序列,則 ^n
指向序列開頭位移 length - n
的項目。 例如,^1
指向序列的最後一個項目,則 ^length
指向序列的第一個項目。
int[] xs = [0, 10, 20, 30, 40];
int last = xs[^1];
Console.WriteLine(last); // output: 40
List<string> lines = ["one", "two", "three", "four"];
string prelast = lines[^2];
Console.WriteLine(prelast); // output: three
string word = "Twenty";
Index toFirst = ^word.Length;
char first = word[toFirst];
Console.WriteLine(first); // output: T
如上述範例所示,運算式 ^e
的類型為 System.Index。 在運算式 ^e
中,e
的結果必須隱含地轉換成 int
。
您也可以使用 ^
運算子搭配範圍運算子來建立索引範圍。 如需詳細資訊,請參閱索引和範圍。
從 C# 13 開始,end 運算子的索引可用於物件初始設定式中。
範圍運算子 ..
..
運算子會指定索引範圍的開頭與結尾作為其運算子。 左側運算元是範圍的內含開始。 右運算元是範圍的排除結尾。 任一運算元皆可以是序列開頭或序列結尾的索引,如下列範例所示:
int[] numbers = [0, 10, 20, 30, 40, 50];
int start = 1;
int amountToTake = 3;
int[] subset = numbers[start..(start + amountToTake)];
Display(subset); // output: 10 20 30
int margin = 1;
int[] inner = numbers[margin..^margin];
Display(inner); // output: 10 20 30 40
string line = "one two three";
int amountToTakeFromEnd = 5;
Range endIndices = ^amountToTakeFromEnd..^0;
string end = line[endIndices];
Console.WriteLine(end); // output: three
void Display<T>(IEnumerable<T> xs) => Console.WriteLine(string.Join(" ", xs));
如上述範例所示,運算式 a..b
的類型為 System.Range。 在運算式 a..b
中,a
和 b
的結果必須隱含地轉換成 Int32 或 Index。
重要
值為負數時,從 int
隱含地轉換到 Index
會擲回 ArgumentOutOfRangeException。
您可以省略 ..
運算子的任何運算元,以取得開放式範圍:
a..
相當於a..^0
..b
相當於0..b
..
相當於0..^0
int[] numbers = [0, 10, 20, 30, 40, 50];
int amountToDrop = numbers.Length / 2;
int[] rightHalf = numbers[amountToDrop..];
Display(rightHalf); // output: 30 40 50
int[] leftHalf = numbers[..^amountToDrop];
Display(leftHalf); // output: 0 10 20
int[] all = numbers[..];
Display(all); // output: 0 10 20 30 40 50
void Display<T>(IEnumerable<T> xs) => Console.WriteLine(string.Join(" ", xs));
下表顯示各種表示集合範圍的方式:
範圍運算子運算式 | 描述 |
---|---|
.. |
集合中所有的值。 |
..end |
從開頭到 end 排除的值。 |
start.. |
從 start 內含到結尾的值。 |
start..end |
從 start 內含到 end 排除的值。 |
^start.. |
從 start 內含到結束計數的值。 |
..^end |
從開始到 end 從結尾排除計算的值。 |
start..^end |
從 start 內含到 end 從結尾排除計算的值。 |
^start..^end |
從 start 內含到 end 排除皆從結尾計算的值。 |
下列範例示範如何使用上表中呈現的所有範圍的效果:
int[] oneThroughTen =
[
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
];
Write(oneThroughTen, ..);
Write(oneThroughTen, ..3);
Write(oneThroughTen, 2..);
Write(oneThroughTen, 3..5);
Write(oneThroughTen, ^2..);
Write(oneThroughTen, ..^3);
Write(oneThroughTen, 3..^4);
Write(oneThroughTen, ^4..^2);
static void Write(int[] values, Range range) =>
Console.WriteLine($"{range}:\t{string.Join(", ", values[range])}");
// Sample output:
// 0..^0: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
// 0..3: 1, 2, 3
// 2..^0: 3, 4, 5, 6, 7, 8, 9, 10
// 3..5: 4, 5
// ^2..^0: 9, 10
// 0..^3: 1, 2, 3, 4, 5, 6, 7
// 3..^4: 4, 5, 6
// ^4..^2: 7, 8
如需詳細資訊,請參閱索引和範圍。
..
權杖也會用作集合運算式中的散佈元素。
運算子是否可多載
.
、()
、^
和 ..
運算子無法多載。 []
運算子也會視為不可多載的運算子。 請使用索引子以支援使用使用者定義型別編製索引。
C# 語言規格
如需詳細資訊,請參閱 C# 語言規格的下列幾節:
如需有關索引和範圍的詳細資訊,請參閱功能提案注意事項 \(英文\)。