Operatoren und Ausdrücke für den Memberzugriff (C#-Referenz)

Sie können die folgenden Operatoren und Ausdrücke zum Zugriff auf einen Typmember verwenden:

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 = new int[] { 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:
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

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.

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.

Sie verwenden eckige Klammern auch, um Attribute anzugeben:

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

NULL-bedingte Operatoren „?.“ und „?[]“

Ein in C# 6 und höher verfügbarer NULL-bedingter Operator wendet nur dann einen Memberzugriffsvorgang (?.) oder Elementzugriffsvorgang (?[]) auf seinen Operanden an, wenn dieser Operand als ungleich NULL ausgewertet wird. Andernfalls gibt er null zurück. Dies bedeutet:

  • 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

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
using System;
using System.Collections.Generic;
using System.Linq;

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

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(new int[0]));  // output: 0
Console.WriteLine(GetSumOfFirstTwoOrDefault(new[] { 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.

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(…)

Dass Code dem folgenden Code entspricht, den Sie in C# 5 oder früher verwenden würden:

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

Dies 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. 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.

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

var numbers = new List<int>();
numbers.Add(10);
numbers.Add(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 ^

Der ^-Operator ist in C# 8.0 und höher verfügbar und gibt die Elementposition vom Ende einer Sequenz 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 = 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

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.

Bereichsoperator .

Der Operator .., der in C# 8.0 und höher verfügbar ist, 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 = 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));

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 int oder Index konvertierbar sein.

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

Weitere Informationen finden Sie unter Indizes und Bereiche.

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