次の方法で共有


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

型のメンバーにアクセスするには、いくつかの演算子と式を使用します。 メンバー アクセス演算子には、メンバー アクセス (.)、配列要素、インデクサー アクセス ([])、インデックスの from-end (^)、範囲 (..)、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

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

インデクサー演算子 []

通常、角かっこ [] は、配列、インデクサー、またはポインター要素へのアクセスに使用されます。 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 がスローされます。

前述の例が示すように、配列型の宣言と配列インスタンスのインスタンス化にも角かっこを使用します。

配列の詳細については、「配列」を参照してください。

インデクサーへのアクセス

次の例では、インデクサーへのアクセスを示すために .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() {}

さらに、角かっこを使用して、パターン マッチングまたはテストで使用する リスト パターン を指定できます。

arr is ([1, 2, ..])
//Specifies that an array starts with (1, 2)

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 が返された場合、残りのチェーンは実行されません。 次の例では、BA と評価されると null は評価されず、C または AB と評価されると null は評価されません。

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

A は null になる可能性があるが、A が null でない場合は B および C が null にならない場合は、null 条件演算子を A に適用するだけで済みます。

A?.B.C();

前の例では、B が null の場合、C() は評価されず、A は呼び出されません。 ただし、チェーン メンバーのアクセスが (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?.Length < 2false の場合、numbersnull と評価されます。

注意

?. 演算子は、左側のオペランドを多くても一度しか評価せず、null 以外であると確認された後にnullに変更されないことを保証します。

C# 14 以降では、参照型に対する null 条件付きアクセス式 (?.?[]) を使用して割り当てが許可されます。 たとえば、次の方法を参照してください。

person?.FirstName = "Scott";
messages?[5] = "five";

前の例は、null である可能性がある参照型のプロパティとインデックス付き要素への割り当てを示しています。 この割り当ての重要な動作は、左側が null でないことがわかっている場合にのみ、 = の右側の式が評価されることです。 たとえば、次のコードでは、GenerateNextIndex配列が null でない場合にのみ、関数valuesが呼び出されます。 values配列が null の場合、GenerateNextIndexは呼び出されません。

person?.FirstName = "Scott";
messages?[5] = "five";

つまり、上記のコードは、null チェックに if ステートメントを使用した次のコードと同じです。

if (values is not null)
{
    values[2] = GenerateNextIndex();
}

代入に加えて、+=など、任意の形式の-=が許可されます。 ただし、インクリメント (++) とデクリメント (--) は許可されません。

この機能強化では、null 条件式は変数として分類されません。 ref割り当てることはできません。また、ref変数に割り当てたり、refまたはout引数としてメソッドに渡したりすることもできません。

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

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

PropertyChanged?.Invoke(…)

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

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

上記の例は、null 以外の handler のみが呼び出されるようにするためのスレッドセーフな方法です。 デリゲート インスタンスは不変であるため、handler ローカル変数によって参照されるオブジェクトを変更できるスレッドはありません。 具体的には、別のスレッドによって実行されるコードが PropertyChanged イベントから登録解除され、PropertyChanged が呼び出される前に nullhandler になる場合、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 型は、int または Count という名前の Length プロパティと、アクセス可能な get アクセサーを持つ型です。 コレクション式も、countable 型に依存します。

注意

1 次元配列は カウント可能です。 多次元配列は含まれません。 ^および.. (範囲) 演算子は、多次元配列では使用できません。

^ 演算子は、シーケンスの末尾からの要素の位置を示します。 長さが 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# 言語仕様」の次のセクションを参照してください。

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

関連項目