成員存取運算子和運算式 - 點、索引子和調用運算子。

您可以使用數個運算子和運算式來存取類型成員。 這些運算子包括成員存取 (.) 、陣列元素或索引子存取 [] () 、從端 ^ (索引) 、範圍 .. () 、null 條件運算子 ?. (和 ?[]) ,以及方法調用 () () 。 其中包括 null 條件式 成員存取 (.?) ,以及索引子存取 () ?[] 運算子。

成員存取運算式 .

您會使用 . 語彙基元來存取命名空間或類型的成員,如下列範例所示:

  • 使用 . 來存取命名空間內的巢狀命名空間,如指示詞的using下列範例所示:
using System.Collections.Generic;
  • 使用 . 來形成「限定名稱」以存取命名空間內的類型,如下列程式碼所示:
System.Collections.Generic.IEnumerable<int> numbers = new int[] { 1, 2, 3 };

using 使用 指示詞來選擇性地使用限定名稱。

  • 使用 . 來存取類型成員 (靜態及非靜態),如下列程式碼所示:
var constants = new List<double>();
constants.Add(Math.PI);
constants.Add(Math.E);
Console.WriteLine($"{constants.Count} values to show:");
Console.WriteLine(string.Join(", ", constants));
// Output:
// 2 values to show:
// 3.14159265358979, 2.71828182845905

您也可以使用 . 來存取擴充方法

索引子運算子 []

中括弧 ([]) 通常用於陣列、索引子或指標元素存取。

陣列存取

以下範例將示範如何存取陣列元素:

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

如上述範例所示,您也會在宣告陣列類型或將陣列執行個體具現化時使用方括弧。

如需陣列的詳細資訊,請參閱陣列

索引子存取

下列範例會使用 .NET Dictionary<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?.xnull

  • 如果 a 評估為非 Null,則 或 a?[x] 的結果 a?.x 會分別與 或 a[x] 的結果 a.x 相同。

    注意

    如果 a.xa[x] 擲回例外狀況, a?.xa?[x] 會針對非 Null a 擲回相同的例外狀況。 例如,如果 a 是非 Null 陣列實例,而且 x 超出 的 a 界限, a?[x] 則會擲回 IndexOutOfRangeException

Null 條件運算子會執行最少運算。 換句話說,如果條件式成員或項目存取作業鏈結中的一個作業傳回 null,則鏈結的其餘部分不會執行。 在下列範例中, B 如果 評估為 null ,且評估為 ,則 C 不會評估或 AB 評估為 nullA

A?.B?.Do(C);
A?.B?[C];

如果 A 可能是 null,但如果 BC A 不是 Null,則不會是 null,則您只需要將 Null 條件運算子套用至 A

A?.B.C();

在上述範例中, B 不會評估 ,如果 C()A 為 null,則不會呼叫 。 不過,如果鏈結的成員存取中斷,例如,括弧如 中的 (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

var numberSets = new List<double[]>
{
    new[] { 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 FullName Name { get; set; }
}

public class FullName
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public void Write()
    {
        Console.WriteLine($"{FirstName} {LastName}");
    }
}

上述兩個範例中的第一個範例也會使用Null 聯合運算子 ??來指定替代運算式,以在 Null 條件運算的結果為 null 時進行評估。

如果 a.xa[x] 屬於不可為 Null 的實值型別 Ta?.xa?[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(new int[0]));  // output: 0
Console.WriteLine(GetSumOfFirstTwoOrDefault(new[] { 3, 4, 5 }));  // output: 7

在上述範例中,如果您未使用 ?? 運算子, numbers?.Length < 2 則評估為 false 當 為 nullnumbers

注意

?.運算子會評估其左側運算元不超過一次,保證在驗證為非 Null 之後無法將其變更 null 為 。

Null 條件成員存取運算子 ?. 也被稱為 Elvis 運算子。

安全執行緒委派引動流程

使用 ?. 運算子來檢查委派是否為非 Null,然後以安全執行緒方式叫用它 (例如當您引發事件時),如下列程式碼所示:

PropertyChanged?.Invoke(…)

該程式碼相當於下列程式碼:

var handler = this.PropertyChanged;
if (handler != null)
{
    handler(…);
}

上述範例是安全線程的方式,可確保只叫用非 Null handler 。 因為委派實例是不可變的,所以沒有任何執行緒可以變更區域變數所 handler 參考的物件。 特別是,如果另一個執行緒執行的程式碼會取消訂閱事件, PropertyChanged 並在 PropertyChanged 叫用之前 handler 變成 null ,則 所 handler 參考的物件仍不會受到影響。

調用運算式 ()

使用括弧 () 來呼叫方法或叫用委派

下列範例示範如何呼叫方法 (使用或不使用引數),以及叫用委派:

Action<int> display = s => Console.WriteLine(s);

var numbers = new List<int>();
numbers.Add(10);
numbers.Add(17);
display(numbers.Count);   // output: 2

numbers.Clear();
display(numbers.Count);   // output: 0

當您使用 new 運算子叫用建構函式時,您也會使用括號。

() 的其他用法

您也可以使用括弧來調整評估運算式中作業的順序。 如需詳細資訊,請參閱 C# 運算子

Cast 運算式 \(其能執行明確類型轉換\) 也會使用括號。

結束運算子 ^ 的索引

^運算子表示序列結尾的專案位置。 若為長度 length 序列, ^n 則指向序列開頭位移 length - n 的專案。 例如, ^1 指向序列的最後一個專案,並 ^length 指向序列的第一個專案。

int[] xs = new[] { 0, 10, 20, 30, 40 };
int last = xs[^1];
Console.WriteLine(last);  // output: 40

var lines = new List<string> { "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

如上述範例所示,expression ^e 的類型為 System.Index 。 在運算式 ^e 中,的結果 e 必須隱含轉換成 int

您也可以使用 ^ 運算子搭配 range 運算子 來建立索引範圍。 如需詳細資訊,請參閱 索引和範圍

範圍運算子 ..

..運算子會指定索引範圍的開始和結尾做為其運算元。 左側運算元是範圍的 內含 開始。 右運算元是範圍的 獨佔 結尾。 任一運算元可以是序列開頭或序列結尾的索引,如下列範例所示:

int[] numbers = new[] { 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));

如上述範例所示,expression a..b 的類型為 System.Range 。 在運算式 a..b 中,和 b 的結果 a 必須隱含地轉換成 Int32Index

重要

當值為負數時,從 int 隱含轉換到 Index 擲回 ArgumentOutOfRangeException

您可以省略運算子的任何運算元 .. ,以取得開放式範圍:

  • a.. 相當於 a..^0
  • ..b 相當於 0..b
  • .. 相當於 0..^0
int[] numbers = new[] { 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# 語言規格的下列幾節:

如需索引和範圍的詳細資訊,請參閱 功能提案附注

另請參閱