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.
.
(Memberzugriff): für den Zugriff auf einen Member eines Namespaces oder Typs[]
(Zugriff auf Arrayelement oder Indexer) : Zugriff auf ein Arrayelement oder einen Typindexer?.
und?[]
(NULL-bedingte Operatoren): Ausführen eines Member- oder Elementzugriffs nur dann, wenn ein Operand ungleich NULL ist.()
(Aufruf): Aufrufen einer Methode, auf die zugegriffen wurde, oder Aufrufen eines Delegaten^
(Index vom Ende): Angeben, dass die Elementposition vom Ende einer Sequenz erfolgt...
Bereich: Angeben eines Bereichs von Indizes, mit dem ein Bereich von Sequenzelementen abgerufen werden kann.
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 einerusing
-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
alsnull
ausgewertet wird, ist das Ergebnis vona?.x
odera?[x]
null
.Wenn
a
in einen Wert ungleich NULL ausgewertet wird, ist das Ergebnis vona?.x
odera?[x]
mit dem Ergebnis vona.x
bzw.a[x]
identisch.Hinweis
Wenn
a.x
odera[x]
eine Ausnahme auslöst, würdena?.x
odera?[x]
füra
ungleich NULL dieselbe Ausnahme auslösen. Wenna
z. B. eine Arrayinstanz ungleich NULL ist undx
außerhalb der Grenzen vona
liegt, lösta?[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..
entsprichta..^0
..b
entspricht0..b
..
entspricht0..^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.