メンバー アクセス演算子と式 - ドット、インデクサー、呼び出しの演算子。

型のメンバーにアクセスするには、いくつかの演算子と式を使用します。 このような演算子には、メンバー アクセス (.)、配列要素またはインデクサーのアクセス ([])、末尾からのインデックス (^)、範囲 (..)、null 条件演算子 (?. および ?[])、メソッド呼び出し (()) があります。 これらには、null 条件付き メンバー アクセス (?.)、およびインデクサー アクセス (?[]) 演算子が含まれます。

メンバー アクセス式 .

以下の例に示すように、名前空間のメンバーまたは型にアクセスするために . トークンを使います。

  • 次の .の例に示すように、. を使って、名前空間内の入れ子になった名前空間にアクセスします。
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

また、. を使って.にアクセスすることもできます。

インデクサー演算子 []

通常、角かっこ [] は、配列、インデクサー、またはポインター要素へのアクセスに使用されます。

配列へのアクセス

次の例は、配列要素へのアクセス方法を示しています。

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 を返します。 つまり、

  • anull と評価された場合、a?.x または a?[x] の結果は null です。

  • a が null 以外と評価された場合、a?.x または a?[x] の結果は、a.x または a[x] の結果とそれぞれ同じです。

    注意

    a.x または a[x] が例外をスローした場合は、a?.x または a?[x] が、null 以外の a に対して同じ例外をスローします。 たとえば、a が null 以外の配列インスタンスで、xaの範囲外にある場合、a?[x]IndexOutOfRangeException をスローします。

Null 条件演算子はショートサーキットです。 つまり、条件付きのメンバーまたは要素アクセス操作のチェーン内にある 1 つの操作から null が返された場合、残りのチェーンは実行されません。 次の例では、Anull と評価されると B は評価されず、A または Bnull と評価されると 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}");
}

前の 2 つの例の 1 番目では、null 条件演算の結果が null の場合に評価する代替の式を指定するために、null 合体演算子 ?? も使用しています。

a.x または a[x] が null 非許容値型の T の場合、a?.x または a?[x] は対応する a.xT? になります。 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

前の例では、?? 演算子を使用しなければ、numbersnull の場合、numbers?.Length < 2false と評価されます。

注意

?. 演算子では、左側のオペランドが 1 回だけ評価され、null 以外として検証された後に null に変更できないことが保証されます。

Null 条件メンバー アクセス演算子 ?. は Elvis 演算子とも呼ばれます。

スレッドセーフなデリゲートの呼び出し

次のコードに示すように、?. 演算子を使用してデリゲートが null 以外かどうかを確認し、それをスレッドセーフな方法で呼び出します (たとえば、?.場合)。

PropertyChanged?.Invoke(…)

このコードは次のコードと同等です。

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

上記の例は、null 以外の handler のみが呼び出されるようにするためのスレッドセーフな方法です。 デリゲート インスタンスは不変であるため、handler ローカル変数によって参照されるオブジェクトを変更できるスレッドはありません。 具体的には、別のスレッドによって実行されるコードが PropertyChanged イベントから登録解除され、handler が呼び出される前に PropertyChangednull になる場合、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

かっこは、new 演算子を使用してコンストラクターを呼び出すときにも使用します。

() の他の使用方法

式に含まれる演算を評価する順序を調整する場合にもかっこを使用します。 詳細については、C# 演算子に関するページを参照してください。

明示的な型変換を実行するキャスト式でも、かっこが使われます。

末尾からのインデックス演算子 ^

インデックス演算子と範囲演算子は、countable 型で使用できます。 countable 型は、Count または Length という名前の int プロパティと、アクセス可能な get アクセサーを持つ型です。 コレクション式も、countable 型に依存します。

^ 演算子は、シーケンスの末尾からの要素の位置を示します。 長さが 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

前の例で示すように、式 ^eSystem.Index 型です。 式 ^eで、e の結果は int に暗黙に変換される必要があります。

さらに、^ 演算子を^ と組み合わせて使用してインデックスの範囲を作成することもできます。 詳細については、「インデックスと範囲」を参照してください。

C# 13 以降では、終了演算子の Index をオブジェクト初期化子の中で使用できます。

範囲演算子 ..

.. 演算子では、インデックス範囲の開始と終了をオペランドとして指定します。 左側のオペランドは "包含的" で、範囲の先頭を含みます。 右側のオペランドは "排他的" で、範囲の末尾を含みません。 次の例で示すように、どちらのオペランドであっても、シーケンスの先頭または末尾からのインデックスとすることができます。

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..bSystem.Range 型です。 式 a..b で、a および b の結果は暗黙に Int32 または Index に変換される必要があります。

重要

int から Index への暗黙的な変換では、値が負の場合に ArgumentOutOfRangeException がスローされます。

.. 演算子のオペランドのいずれかを省略して、変更可能な範囲を取得することができます。

  • a..a..^0 と同じです。
  • ..b0..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 start から end (これを含まない) までの値。
start.. start (これを含む) から end までの値。
start..end start (これを含む) から end (これを含まない) までの値。
^start.. start (これを含む) から end までの値であり、end からカウントされます。
..^end start から end (これを含まない) までの値であり、end からカウントされます。
start..^end start (これを含む) から end までの値であり、end からカウントされます。
^start..^end start (これを含む) から end (これを含まない) までの値であり、両方とも 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

詳細については、「インデックスと範囲」を参照してください。

.. トークンは、コレクション式で spread 演算子としても使用されます。

演算子のオーバーロード可/不可

.()^.. の各演算子はオーバーロードできません。 [] 演算子も、オーバーロードできない演算子と見なされます。 ユーザー定義型を使用したインデックス作成をサポートするには、インデクサーを使用してください。

C# 言語仕様

詳細については、「C# 言語仕様」の次のセクションを参照してください。

インデックスと範囲について詳しくは、機能提案メモを参照してください。

関連項目