Teilen über


Memberzugriffsoperatoren und -ausdrücke: Punkt-, Indexer- und Aufrufoperatoren

Sie können verschiedene Operatoren und Ausdrücke zum Zugriff auf einen Typmember verwenden. Zu diesen Operatoren zählen der Memberzugriffsausdruck ., der Arrayelement- oder Indexerzugriffsoperator [], der Index-from-End-Operator ^, der Bereichsoperator .., die NULL-bedingten Operatoren ?. und ?[] und der Methodenaufrufsoperator (). Der NULL-bedingte Memberzugriffsausdruck ?. und der Indexerzugriffsoperator ?[] zählen ebenfalls dazu.

Memberzugriffsausdruck .

Sie verwenden das .-Token für den Zugriff auf einen Member eines Namespace oder eines Typs, wie die folgenden Beispiele veranschaulichen:

  • Verwenden Sie . für den Zugriff auf einen geschachtelten Namespace innerhalb eines Namespace, wie im folgenden Beispiel einer using-Anweisung gezeigt:
using System.Collections.Generic;
  • Verwenden Sie ., um einen qualifizierten Namen zu bilden, um auf einen Typ innerhalb eines Namespace zuzugreifen, wie im folgenden Code gezeigt:
System.Collections.Generic.IEnumerable<int> numbers = [1, 2, 3];

Verwenden Sie eine using-Anweisung, um die Verwendung qualifizierter Namen optional zu machen.

  • Verwenden Sie . für den Zugriff auf Typmember, statische und nicht statische, wie im folgenden Code gezeigt:
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

Sie können auch . verwenden, um auf eine Erweiterungsmethode zuzugreifen.

Indexeroperator []

Eckige Klammern ([]) werden in der Regel für den Zugriff auf Arrays, Indexer oder Zeigerelemente verwendet. Ab C# 12 schließt [] ein Sammlungsausdruck ein.

Arrayzugriff

Im folgenden Beispiel wird der Zugriff auf Elemente des Arrays veranschaulicht:

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

Wenn ein Arrayindex sich außerhalb der Grenzen der entsprechenden Dimension eines Arrays befindet, wird eine IndexOutOfRangeException ausgelöst.

Wie im vorherigen Beispiel gezeigt, verwenden Sie eckige Klammern auch zur Deklaration eines Arraytyps oder Instanziierung von Arrayinstanzen.

Weitere Informationen zu Arrays finden Sie unter Arrays.

Indexerzugriff

Im folgenden Beispiel wird der Indexerzugriff anhand des .NET Dictionary<TKey,TValue>-Typs veranschaulicht:

var dict = new Dictionary<string, double>();
dict["one"] = 1;
dict["pi"] = Math.PI;
Console.WriteLine(dict["one"] + dict["pi"]);  // output: 4.14159265358979

Mit Indexern können Sie Instanzen eines benutzerdefinierten Typs auf ähnliche Weise wie ein Array indizieren. Im Gegensatz zu Arrayindizes, die ganze Zahlen sein müssen, können die Indexerparameter mit einem beliebigen Typ deklariert werden.

Weitere Informationen über Indexer finden Sie unter Indexer.

Andere Verwendungen von „[]“

Weitere Informationen zum Zeigerelementzugriff finden Sie im Abschnitt Zeigerelementzugriff-Operator [] im Artikel Operatoren im Zusammenhang mit Zeigern. Informationen zu Sammlungsausdrücken finden Sie im Artikel zu Sammlungsausdrücken.

Sie verwenden eckige Klammern auch, um Attribute anzugeben:

[System.Diagnostics.Conditional("DEBUG")]
void TraceMethod() {}

NULL-bedingte Operatoren ?. und ?[]

Ein bedingter NULL-Operator wendet den Memberzugriffsoperator (?.) oder Elementzugriffsoperator (?[]) nur dann auf seinen Operanden an, wenn dieser mit ungleich NULL ausgewertet wird. Andernfalls gibt er null zurück. Anders gesagt:

  • Wenn a als null ausgewertet wird, ist das Ergebnis von a?.x oder a?[x]null.

  • Wenn a in einen Wert ungleich NULL ausgewertet wird, ist das Ergebnis von a?.x oder a?[x] mit dem Ergebnis von a.x bzw. a[x] identisch.

    Hinweis

    Wenn a.x oder a[x] eine Ausnahme auslöst, würden a?.x oder a?[x] für a ungleich NULL dieselbe Ausnahme auslösen. Wenn a z. B. eine Arrayinstanz ungleich NULL ist und x außerhalb der Grenzen von a liegt, löst a?[x] eine IndexOutOfRangeException aus.

Die NULL-bedingten Operatoren sind Kurzschlussoperatoren. D.h., wenn ein Vorgang in einer Kette von bedingten Member- oder Elementzugriffsvorgängen null zurückgibt, wird der Rest der Kette nicht ausgeführt. Im folgenden Beispiel wird B nicht ausgewertet, wenn A als null ausgewertet wird, und C wird nicht ausgewertet, wenn A oder B als null ausgewertet wird:

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

Wenn A NULL sein könnte, aber B und C nicht NULL wären, wenn A nicht NULL ist, müssen Sie nur den NULL-bedingten Operator auf A anwenden:

A?.B.C();

Im vorherigen Beispiel wird B nicht ausgewertet und C() nicht aufgerufen, wenn A NULL ist. Wenn der verkettete Memberzugriff jedoch unterbrochen wird (z. B. durch Klammern wie in (A?.B).C()), erfolgt kein Kurzschluss.

In den folgenden Beispielen wird die Verwendung der ?.- und ?[]-Operatoren veranschaulicht:

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}");
}

Im ersten der vorangehenden zwei Beispiele wird auch der NULL-Sammeloperator?? zum Angeben eines alternativen Ausdrucks zum Auswerten verwendet, falls das Ergebnis eines NULL-bedingten Vorgangs null ist.

Wenn a.x oder a[x] vom Werttyp T ist, der keine NULL-Werte zulässt, ist a?.x oder a?[x] vom entsprechenden Werttyp T?, der keine NULL-Werte zulässt. Wenn Sie einen Ausdruck vom Typ T benötigen, wenden Sie den NULL-Sammeloperator ?? auf einen NULL-bedingten Ausdruck an, wie im folgenden Beispiel gezeigt:

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

Wenn Sie im vorherigen Beispiel nicht den ??-Operator verwenden und numbers den Wert null hat, wird numbers?.Length < 2 als false ausgewertet.

Hinweis

Der ?.-Operator wertet seinen linken Operanden nicht mehr als einmal aus, um sicherzustellen, dass er nicht in null geändert werden kann, nachdem bestätigt wurde, dass er ungleich NULL ist.

Der NULL-bedingte Memberzugriffsoperator ?. wird auch als Elvis-Operator bezeichnet.

Threadsicherer Delegataufruf

Verwenden Sie den ?.-Operator, um zu überprüfen, ob ein Delegat ungleich NULL ist, und ihn auf threadsichere Weise aufzurufen (z.B. wenn Sie ein Ereignis auslösen), wie der folgende Code zeigt:

PropertyChanged?.Invoke(…)

Dieser Code entspricht dem folgenden Code:

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

Das vorangehende Beispiel ist eine threadsichere Möglichkeit, um sicherzustellen, dass nur ein handler ungleich NULL aufgerufen wird. Da Delegatinstanzen unveränderlich sind, kann kein Thread das Objekt ändern, auf das von der lokalen handler-Variable verwiesen wird. Insbesondere wenn der von einem anderen Thread ausgeführte Code das Abonnement des PropertyChanged-Ereignisses aufhebt und PropertyChanged zu null wird, bevor handler aufgerufen wird, bleibt das Objekt unverändert, auf das von handler verwiesen wird.

Aufrufausdruck „()“

Verwenden Sie Klammern () zum Aufrufen einer Methode, oder rufen Sie einen Delegaten auf.

Im folgenden Beispiel wird der Aufruf einer Methode mit oder ohne Argumente sowie eines Delegaten veranschaulicht:

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

List<int> numbers =
[
    10,
    17
];
display(numbers.Count);   // output: 2

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

Klammern verwenden Sie auch beim Aufrufen eines Konstruktors mit dem new-Operator.

Andere Verwendungen von „()“

Mit Klammern passen Sie auch die Reihenfolge an, in der Vorgänge in einem Ausdruck ausgewertet werden sollen. Weitere Informationen finden Sie unter C#-Operatoren.

Cast-Ausdrücke, die explizite Typkonvertierungen ausführen, verwenden ebenfalls Klammern.

Index vom Endeoperator ^

Index- und Bereichsoperatoren können mit einem Typ verwendet werden, der zählbar ist. Ein zählbarer Typ ist ein Typ, der eine int-Eigenschaft namens Count oder Length mit einer zugänglichen get-Zugriffsmethode hat. Auch Sammlungsausdrücke basieren auf zählbaren Typen.

Der ^-Operator gibt die Elementposition vom Ende einer Sequenz ausgehend an. Für eine Sequenz der Länge length verweist ^n auf das Element mit dem Offset length - n vom Beginn einer Sequenz. ^1 zeigt beispielsweise auf das letzte Element einer Sequenz, und ^length zeigt auf das erste Element einer Sequenz.

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

Wie das vorherige Beispiel zeigt, weist Ausdruck ^e den Typ System.Index auf. In Ausdruck ^e muss das Ergebnis von e implizit in int konvertierbar sein.

Sie können auch den ^-Operator mit dem Bereichsoperator verwenden, um einen Bereich von Indizes zu erstellen. Weitere Informationen finden Sie unter Indizes und Bereiche.

Ab C# 13 kann der Index vom Endoperator in einem Objektinitialisierer verwendet werden.

Bereichsoperator ..

Der Operator .. gibt den Anfang und das Ende eines Bereichs von Indizes als seine Operanden an. Der linke Operand ist der inklusive Anfang eines Bereichs. Der rechte Operand ist das exklusive Ende eines Bereichs. Beide Operanden können ein Index vom Anfang oder vom Ende einer Sequenz sein, wie das folgende Beispiel zeigt:

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));

Wie das vorherige Beispiel zeigt, weist der Ausdruck a..b den Typ System.Range auf. In Ausdruck a..b muss das Ergebnis von a und b implizit in Int32 oder Index konvertierbar sein.

Wichtig

Implizite Konvertierungen von int in Index lösen eine ArgumentOutOfRangeException aus, wenn der Wert negativ ist.

Sie können Operanden des Operators .. auslassen, um einen Bereich ohne Ende abzurufen:

  • a.. entspricht a..^0
  • ..b entspricht 0..b
  • .. entspricht 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));

In der folgende Tabelle werden verschiedene Möglichkeiten aufgeführt, um Sammlungsbereiche auszudrücken:

Bereichsoperatorausdruck BESCHREIBUNG
.. Alle Werte in der Sammlung
..end Werte von Anfang bis exklusive end
start.. Werte von inklusive start bis zum Ende
start..end Werte von inklusive start bis exklusive end
^start.. Werte von inklusive start bis zum Ende, vom Ende aus gezählt
..^end Werte vom Anfang bis exklusive end, vom Ende aus gezählt
start..^end Werte von inklusive start bis exklusive end, vom Ende aus gezählt
^start..^end Werte von inklusive start bis exklusive end, beide vom Ende aus gezählt

Im folgenden Beispiel wird die Auswirkung aller in der vorherigen Tabelle aufgeführten Bereiche veranschaulicht:

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

Weitere Informationen finden Sie unter Indizes und Bereiche.

Das ..-Token wird auch für das Spread-Element in einem Sammlungsausdruck verwendet.

Operatorüberladbarkeit

Die Operatoren ., (), ^ und .. können nicht überladen werden. Der []-Operator wird auch als nicht überladbarer Operator betrachtet. Verwenden Sie Indexer zur Unterstützung der Indizierung mit benutzerdefinierten Typen.

C#-Sprachspezifikation

Weitere Informationen finden Sie in den folgenden Abschnitten der C#-Sprachspezifikation:

Weitere Informationen zu Indizes und Bereichen finden Sie unter Hinweis zum Featurevorschlag.

Siehe auch