Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
13.1 Allgemein
C# stellt eine Vielzahl von Anweisungen bereit.
Hinweis: Die meisten dieser Anweisungen sind Entwicklern vertraut, die in C und C++ programmiert haben. Hinweisende
statement
: labeled_statement
| declaration_statement
| embedded_statement
;
embedded_statement
: block
| empty_statement
| expression_statement
| selection_statement
| iteration_statement
| jump_statement
| try_statement
| checked_statement
| unchecked_statement
| lock_statement
| using_statement
| yield_statement
| unsafe_statement // unsafe code support
| fixed_statement // unsafe code support
;
unsafe_statement (§24.2) und fixed_statement (§24.7) sind nur im unsicheren Code (§24) verfügbar.
Das Nichtterminal embedded_statement wird für Anweisungen verwendet, die innerhalb anderer Anweisungen erscheinen. Die Anwendung von embedded_statement anstelle von statement schließt die Anwendung von Deklarationsanweisungen und bezeichneten Anweisungen in diesen Kontexten aus.
Beispiel: Der Code
void F(bool b) { if (b) int i = 44; }führt zu einem Kompilierfehler, weil eine
ifAnweisung für ihren Branch ein embedded_statement und nicht einifbenötigt. Wenn dieser Code zulässig wäre, würde die Variableideklariert, aber sie konnte nie verwendet werden. Beachten Sie jedoch, dass das Beispiel gültig ist, indem die Deklaration in einem Block platziertiwird.Endbeispiel
13.2 Endpunkte und Reichweite
Jede Anweisung hat einen Endpunkt. Intuitiv gesehen ist der Endpunkt einer Anweisung die Position, die unmittelbar auf die Anweisung folgt. Die Ausführungsregeln für zusammengesetzte Anweisungen (Anweisungen, die eingebettete Anweisungen enthalten) geben die Aktion an, die ausgeführt wird, wenn das Steuerelement den Endpunkt einer eingebetteten Anweisung erreicht.
Beispiel: Wenn das Steuerelement den Endpunkt einer Anweisung in einem Block erreicht, wird das Steuerelement in die nächste Anweisung im Block übertragen. Endbeispiel
Wenn eine Anweisung möglicherweise durch Ausführung erreicht werden kann, wird die Anweisung als erreichbar bezeichnet. Wenn dagegen keine Möglichkeit besteht, dass eine Anweisung ausgeführt wird, wird die Anweisung als nicht erreichbar bezeichnet.
Beispiel: Im folgenden Code
void F() { Console.WriteLine("reachable"); goto Label; Console.WriteLine("unreachable"); Label: Console.WriteLine("reachable"); }der zweite Aufruf von Console.WriteLine ist nicht erreichbar, da es keine Möglichkeit gibt, dass die Anweisung ausgeführt wird.
Endbeispiel
Eine Warnung wird gemeldet, wenn eine andere Anweisung als throw_statement, Block oder empty_statement nicht erreichbar ist. Es handelt sich insbesondere nicht um einen Fehler, wenn eine Aussage nicht erreichbar ist.
Hinweis: Um zu bestimmen, ob eine bestimmte Anweisung oder ein Endpunkt erreichbar ist, führt ein Compiler Eine Flussanalyse gemäß den für jede Anweisung definierten Reichweitenregeln durch. Die Flussanalyse berücksichtigt die Werte konstanter Ausdrücke (§12.25), die das Verhalten von Anweisungen steuern, aber die möglichen Werte von nicht konstanten Ausdrücken werden nicht berücksichtigt. Anders ausgedrückt wird bei der Steuerungsflussanalyse ein nicht konstanter Ausdruck eines bestimmten Typs als ein Ausdruck betrachtet, der jeden möglichen Wert dieses Typs annehmen kann.
Im Beispiel
void F() { const int i = 1; if (i == 2) Console.WriteLine("unreachable"); }Der boolesche Ausdruck der
ifAnweisung ist ein konstanter Ausdruck, da beide Operanden des==Operators Konstanten sind. Wenn der konstante Ausdruck während der Kompilierung ausgewertet wird, wird der Wertfalseerzeugt, und derConsole.WriteLine-Aufruf wird als nicht erreichbar betrachtet. Allerdings, wenniin eine lokale Variable geändert wirdvoid F() { int i = 1; if (i == 2) Console.WriteLine("reachable"); }der
Console.WriteLineAufruf gilt als erreichbar, obwohl es in Wirklichkeit niemals ausgeführt wird.Hinweisende
Der Block eines Funktionselements oder einer anonymen Funktion gilt immer als erreichbar. Durch die aufeinanderfolgende Auswertung der Reichweitenregeln jeder Anweisung in einem Block kann die Erreichbarkeit einer gegebenen Anweisung ermittelt werden.
Beispiel: Im folgenden Code
void F(int x) { Console.WriteLine("start"); if (x < 0) Console.WriteLine("negative"); }die Reichweite des zweiten
Console.WriteLinewird wie folgt bestimmt:
- Die erste
Console.WriteLineAusdrucksanweisung ist erreichbar, da der Block derFMethode erreichbar ist (§13.3).- Der Endpunkt der ersten
Console.WriteLineAusdrucksanweisung ist erreichbar, da diese Anweisung erreichbar ist (§13.7 und §13.3).- Die
ifAnweisung ist erreichbar, da der Endpunkt der erstenConsole.WriteLineAusdrucksanweisung erreichbar ist (§13.7 und §13.3).- Die zweite
Console.WriteLineAusdrucksanweisung ist erreichbar, da der boolesche Ausdruck derifAnweisung nicht den Konstantenwertfalseaufweist.Endbeispiel
Es gibt zwei Situationen, in denen es ein Kompilierfehler ist, wenn der Endpunkt einer Anweisung nicht erreicht werden kann:
Da die
switch-Anweisung es nicht zulässt, dass ein switch-Abschnitt zum nächsten switch-Abschnitt „durchfällt“, ist es ein Kompilierfehler, wenn der Endpunkt der Anweisungsliste eines switch-Abschnitts erreichbar ist. Wenn dieser Fehler auftritt, ist es in der Regel ein Hinweis darauf, dass einebreakAnweisung fehlt.Es handelt sich um einen Kompilierungszeitfehler für den Endpunkt des Blocks eines Funktionselements oder einer anonymen Funktion, die einen zu erreichenden Wert berechnet. Wenn dieser Fehler auftritt, ist es in der Regel ein Hinweis darauf, dass eine
returnAnweisung fehlt (§13.10.5).
13.3 Blöcke
13.3.1 Allgemein
Ein Block ermöglicht, mehrere Anweisungen in Kontexten zu schreiben, in denen eine einzelne Anweisung zulässig ist.
block
: '{' statement_list? '}'
;
Ein Block besteht aus einer optionalen Anweisungsliste (§13.3.2), die in geschweiften Klammern eingeschlossen ist. Wenn die Anweisungsliste weggelassen wird, gilt der Block als leer.
Ein Block kann Deklarationsanweisungen enthalten (§13.6). Der Bereich einer lokalen Variablen oder Konstante, die in einem Block deklariert ist, ist der Block.
Ein Block wird wie folgt ausgeführt:
- Wenn der Block leer ist, wird das Steuerelement an den Endpunkt des Blocks übertragen.
- Wenn der Block nicht leer ist, wird die Kontrolle auf die Anweisungsliste übertragen. Wenn und falls die Kontrolle den Endpunkt der Anweisungsliste erreicht, wird die Kontrolle an den Endpunkt des Blocks übertragen.
Die Anweisungsliste eines Blocks ist erreichbar, wenn der Block selbst erreichbar ist.
Der Endpunkt eines Blocks ist erreichbar, wenn der Block leer ist oder der Endpunkt der Anweisungsliste erreichbar ist.
Ein Block , der eine oder yield mehrere Anweisungen (§13.15) enthält, wird als Iteratorblock bezeichnet. Iteratorblöcke werden verwendet, um Funktionsmber als Iteratoren (§15.15) zu implementieren. Einige zusätzliche Einschränkungen gelten für Iteratorblöcke:
- Es handelt sich um einen Kompilierungszeitfehler für eine
returnAnweisung, die in einem Iteratorblock angezeigt wird (aberyield returnAnweisungen sind zulässig). - Es handelt sich um einen Kompilierungszeitfehler für einen Iteratorblock, der einen unsicheren Kontext (§24.2) enthält. Ein Iteratorblock definiert immer einen sicheren Kontext, auch wenn seine Deklaration in einem unsicheren Kontext geschachtelt ist.
13.3.2 Erklärungslisten
Eine Anweisungsliste besteht aus einer oder mehreren Anweisungen, die in Sequenz geschrieben wurden. Anweisungslisten kommen in Blocks (§13.3) und in Switch_blocks (§13.8.3) vor.
statement_list
: statement+
;
Eine Anweisungsliste wird ausgeführt, indem die Kontrolle auf die erste Anweisung übertragen wird. Wenn und wenn das Steuerelement den Endpunkt einer Anweisung erreicht, wird das Steuerelement in die nächste Anweisung übertragen. Wenn und wenn das Steuerelement den Endpunkt der letzten Anweisung erreicht, wird das Steuerelement an den Endpunkt der Anweisungsliste übertragen.
Eine Anweisung in einer Anweisungsliste ist erreichbar, wenn mindestens eine der folgenden Bedingungen erfüllt ist:
- Die Anweisung ist die erste Anweisung und die Anweisungsliste selbst ist erreichbar.
- Der Endpunkt der vorangehenden Anweisung ist erreichbar.
- Die Anweisung ist eine gekennzeichnete Anweisung und die Kennzeichnung wird durch eine erreichbare
goto-Anweisung referenziert.
Der Endpunkt einer Anweisungsliste ist erreichbar, wenn der Endpunkt der letzten Anweisung in der Liste erreichbar ist.
13.4 Die leere Anweisung
Ein empty_statement bewirkt nichts.
empty_statement
: ';'
;
Eine leere Anweisung wird verwendet, wenn es keine Vorgänge gibt, die in einem Kontext ausgeführt werden müssen, in dem eine Anweisung erforderlich ist.
Die Ausführung einer leeren Anweisung überträgt einfach die Steuerung an den Endpunkt der Anweisung. Daher ist der Endpunkt einer leeren Anweisung erreichbar, wenn die leere Anweisung erreichbar ist.
Beispiel: Eine leere Anweisung kann beim Schreiben einer
whileAnweisung mit einem NULL-Text verwendet werden:bool ProcessMessage() {...} void ProcessMessages() { while (ProcessMessage()) ; }Eine leere Anweisung kann auch verwendet werden, um eine Kennzeichnung kurz vor dem abschließenden „
}“ eines Blocks zu deklarieren:void F(bool done) { ... if (done) { goto exit; } ... exit: ; }Endbeispiel
13.5 Anweisungen mit Bezeichnung
Ein labeled_statement erlaubt es, einer Anweisung eine Kennzeichnung voranzustellen. Beschriftete Anweisungen sind in Blöcken zulässig, sind jedoch nicht als eingebettete Anweisungen zulässig.
labeled_statement
: identifier ':' statement
;
Eine gekennzeichnete Anweisung deklariert eine Kennzeichnung mit dem durch den Identifikator angegebenen Namen. Der Bereich einer Bezeichnung ist der gesamte Block, in dem die Bezeichnung deklariert wird, einschließlich aller geschachtelten Blöcke. Es ist ein Kompilierungsfehler, wenn zwei Labels mit demselben Namen überlappende Gültigkeitsbereiche haben.
Eine Kennzeichnung kann von goto Anweisungen (§13.10.4) innerhalb des Bereichs der Kennzeichnung referenziert werden.
Hinweis: Dies bedeutet, dass
gotoAnweisungen die Steuerung innerhalb von Blöcken und aus Blöcken übertragen können, aber nie in Blöcke. Hinweisende
Bezeichnungen verfügen über einen eigenen Deklarationsbereich und beeinträchtigen keine anderen Bezeichner.
Beispiel: Das Beispiel
int F(int x) { if (x >= 0) { goto x; } x = -x; x: return x; }ist gültig und verwendet den Namen "x" sowohl als Parameter als auch als Bezeichnung.
Endbeispiel
Die Ausführung einer beschrifteten Anweisung entspricht exakt der Ausführung der Anweisung nach der Bezeichnung.
Neben der Reichweite, die durch einen normalen Kontrollfluss bereitgestellt wird, ist eine beschriftete Anweisung erreichbar, wenn auf die Bezeichnung durch eine erreichbare goto Anweisung verwiesen wird, es sei denn, die goto Anweisung befindet sich innerhalb des try Blocks oder eines catch Blocks eines try_statement , der einen finally Block enthält, dessen Endpunkt nicht erreichbar ist, und die beschriftete Anweisung liegt außerhalb des try_statement.
13.6 Deklarationsanweisungen
13.6.1 Allgemein
Ein declaration_statement deklariert eine oder mehrere lokale Variablen, eine oder mehrere lokale Konstanten oder eine lokale Funktion. Deklarationsanweisungen sind in Blockierungen und Switch-Blöcken erlaubt, aber nicht als eingebettete Anweisungen.
declaration_statement
: local_variable_declaration ';'
| local_constant_declaration ';'
| local_function_declaration
;
Eine lokale Variable wird mithilfe einer local_variable_declaration (§13.6.2) deklariert. Eine lokale Konstante wird mithilfe einer local_constant_declaration (§13.6.3) deklariert. Eine lokale Funktion wird mithilfe einer local_function_declaration (§13.6.4) deklariert.
Die deklarierten Namen werden in den nächstgelegenen einschließenden Deklarationsbereich eingefügt (§7.3).
13.6.2 Lokale Variablendeklarationen
13.6.2.1 Allgemein
Ein local_variable_declaration deklariert eine oder mehrere lokale Variablen.
local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
| explicitly_typed_ref_local_variable_declaration
;
Implizit eingegebene Deklarationen enthalten das kontextbezogene Schlüsselwort (§6.4.4), var was zu einer syntaktischen Mehrdeutigkeit zwischen den drei Kategorien führt, die wie folgt aufgelöst werden:
- Wenn es keinen Typ namens
varim Bereich gibt und die Eingabe mit implicitly_typed_local_variable_declaration übereinstimmt, dann wird dieser gewählt; - Andernfalls, wenn ein Typ mit dem Namen
varim Bereich liegt, wird implicitly_typed_local_variable_declaration nicht als mögliche Übereinstimmung betrachtet.
Innerhalb eines local_variable_declaration wird jede Variable von einem Deklarator eingeführt, der einer von implicitly_typed_local_variable_declarator ist, explicitly_typed_local_variable_declarator oder ref_local_variable_declarator für implizit typierte, explizit eingegebene und referenzierte lokale Variablen. Der Deklarator definiert den Namen (Bezeichner) und den Anfangswert (sofern vorhanden) der eingeführten Variablen.
Wenn es mehrere Deklaratoren in einer Deklaration gibt, werden diese von links nach rechts verarbeitet, einschließlich der Initialisierungsausdrücke (§9.4.4.5).
Hinweis: Für eine local_variable_declaration nicht als for_initializer (§13.9.4) oder resource_acquisition (§13.14) entspricht diese links-nach-rechts-Reihenfolge jedem Deklarator innerhalb eines separaten local_variable_declaration. Zum Beispiel:
void F() { int x = 1, y, z = x * 2; }entspricht:
void F() { int x = 1; int y; int z = x * 2; }Hinweisende
Der Wert einer lokalen Variablen wird in einem Ausdruck mithilfe eines simple_name (§12.8.4) abgerufen. An jedem Ort, an dem der Wert abgerufen wird, muss definitiv eine lokale Variable (§9.4) zugewiesen werden. Jede lokale Variable, die von einem local_variable_declaration eingeführt wird, ist zunächst nicht zugewiesen (§9.4.3). Wenn ein Deklarator über einen Initialisierungsausdruck verfügt, wird die eingeführte lokale Variable am Ende des Deklarators als zugewiesen klassifiziert (§9.4.4.5).
Der Bereich einer lokalen Variablen, die von einem local_variable_declaration eingeführt wird, ist wie folgt definiert (§7.7):
- Wenn die Deklaration als for_initializer erfolgt, umfasst der Bereich die for_initializer, for_condition, for_iterator und embedded_statement (§13.9.4);
- Wenn die Deklaration als Resource_acquisition auftritt, dann ist der Bereich der äußerste Block der semantisch äquivalenten Erweiterung der using_statement (§13.14);
- Andernfalls ist der Gültigkeitsbereich der Block, in dem die Deklaration erfolgt.
Es ist ein Fehler, auf eine lokale Variable anhand des Namens in einer Textposition zu verweisen, die dem Deklarator vorausgeht, oder innerhalb eines initialisierenden Ausdrucks innerhalb des Deklarators. Innerhalb des Bereichs einer lokalen Variablen ist es ein Kompilierungszeitfehler, um eine andere lokale Variable, lokale Funktion oder Konstante mit demselben Namen zu deklarieren.
Der ref-safe-Kontext (§9.7.2) einer lokalen ref-Variable ist der ref-safe-Kontext ihrer initialisierenden variable_referenz. Der ref-safe-Kontext von nicht-ref lokalen Variablen ist declaration-block.
13.6.2.2 Implizit typierte lokale Variablendeklarationen
implicitly_typed_local_variable_declaration
: 'var' implicitly_typed_local_variable_declarator
| ref_kind 'var' ref_local_variable_declarator
;
implicitly_typed_local_variable_declarator
: identifier '=' expression
;
Ein implicitly_typed_local_variable_declaration führt eine einzelne lokale Variable, einen Bezeichner ein. Der Ausdruck oder variable_reference muss einen Typ bei der Kompilierung haben, T. Die erste Alternative deklariert eine Variable mit einem Anfangswert von Ausdruck; ihr Typ ist T?, wenn T ein nicht-nullbarer Bezugstyp ist, andernfalls ist ihr Typ T. Die zweite Alternative deklariert eine Ref-Variable mit einem Anfangswert von refvariable_reference. Ihr Typ ist ref T?, wenn T ein Nicht-Null-Bezugstyp ist, andernfalls ist ihr Typ ref T. (ref_kind wird in §15.6.1 beschrieben.)
Beispiel:
var i = 5; var s = "Hello"; var d = 1.0; var numbers = new int[] {1, 2, 3}; var orders = new Dictionary<int,Order>(); ref var j = ref i; ref readonly var k = ref i;Die oben aufgeführten implizit typierten lokalen Variablendeklarationen entsprechen genau den folgenden explizit typierten Deklarationen:
int i = 5; string s = "Hello"; double d = 1.0; int[] numbers = new int[] {1, 2, 3}; Dictionary<int,Order> orders = new Dictionary<int,Order>(); ref int j = ref i; ref readonly int k = ref i;Es folgen falsch implizit typierte lokale Variablendeklarationen:
var x; // Error, no initializer to infer type from var y = {1, 2, 3}; // Error, array initializer not permitted var z = null; // Error, null does not have a type var u = x => x + 1; // Error, anonymous functions do not have a type var v = v++; // Error, initializer cannot refer to v itselfEndbeispiel
13.6.2.3 Explizit typierte lokale Variablendeklarationen
explicitly_typed_local_variable_declaration
: type explicitly_typed_local_variable_declarators
;
explicitly_typed_local_variable_declarators
: explicitly_typed_local_variable_declarator
(',' explicitly_typed_local_variable_declarator)*
;
explicitly_typed_local_variable_declarator
: identifier ('=' local_variable_initializer)?
;
local_variable_initializer
: expression
| array_initializer
;
Ein explicitly_typed_local_variable_declaration führt eine oder mehrere lokale Variablen mit dem angegebenen Typ ein.
Wenn ein local_variable_initializer vorhanden ist, ist sein Typ gemäß den Regeln der einfachen Zuordnung (§12.23.2) oder Arrayinitialisierung (§17.7) angemessen, und sein Wert wird als Anfangswert der Variablen zugewiesen.
13.6.2.4 Explizit typisierte ref lokale Variablendeklarationen
explicitly_typed_ref_local_variable_declaration
: ref_kind type ref_local_variable_declarators
;
ref_local_variable_declarators
: ref_local_variable_declarator (',' ref_local_variable_declarator)*
;
ref_local_variable_declarator
: identifier '=' 'ref' variable_reference
;
Die initialisierende variable_reference muss Typ aufweisen und die gleichen Anforderungen erfüllen wie für eine Refzuordnung (§12.23.3).
Wenn ref_kindref readonly ist, sind die deklarierten Identifier Referenzen auf Variablen, die als schreibgeschützt behandelt werden. Andernfalls, wenn ref_kind ist ref, sind die deklarierten BezeichnerVerweise auf Variablen, die schreibbar sein müssen.
Es ist ein Kompilierfehler, eine lokale ref-Variable oder eine Variable vom Typ ref struct innerhalb einer Methode, die mit dem method_modifierasync deklariert wurde, oder innerhalb eines Iterators (§15.15) zu deklarieren.
13.6.3 Lokale Konstantendeklarationen
Ein local_constant_declaration deklariert eine oder mehrere lokale Konstanten.
local_constant_declaration
: 'const' type constant_declarators
;
constant_declarators
: constant_declarator (',' constant_declarator)*
;
constant_declarator
: identifier '=' constant_expression
;
Der Typ eines local_constant_declaration gibt den Typ der durch die Deklaration eingeführten Konstanten an. Auf den Typ folgt eine Liste von constant_declarators, von denen jede eine neue Konstante einführt. Ein constant_declarator besteht aus einem Bezeichner , der die Konstante benennt, gefolgt von einem "="-Token, gefolgt von einem constant_expression (§12.25), der den Wert der Konstante angibt.
Der Typ und constant_expression einer lokalen Konstantendeklaration entsprechen den gleichen Regeln wie die einer konstanten Memberdeklaration (§15.4).
Der Wert einer lokalen Konstante wird in einem Ausdruck mithilfe einer simple_name (§12.8.4) abgerufen.
Der Bereich einer lokalen Konstante ist der Block, in dem die Deklaration auftritt. Es ist ein Fehler, auf eine lokale Konstante in einer Textposition zu verweisen, die vor dem Ende der constant_declarator liegt.
Eine lokale Konstantendeklaration, die mehrere Konstanten deklariert, entspricht mehreren Deklarationen einzelner Konstanten mit demselben Typ.
13.6.4 Lokale Funktionsdeklarationen
Ein local_function_declaration deklariert eine lokale Funktion.
local_function_declaration
: local_function_modifier* return_type local_function_header
local_function_body
| ref_local_function_modifier* ref_kind ref_return_type
local_function_header ref_local_function_body
;
local_function_header
: identifier '(' parameter_list? ')'
| identifier type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause*
;
local_function_modifier
: ref_local_function_modifier
| 'async'
;
ref_local_function_modifier
: 'static'
| unsafe_modifier // unsafe code support
;
local_function_body
: block
| '=>' null_conditional_invocation_expression ';'
| '=>' expression ';'
;
ref_local_function_body
: block
| '=>' 'ref' variable_reference ';'
;
Grammatikalische Anmerkung: Wenn bei der Erkennung eines local_function_body sowohl die null_conditional_invocation_expression als auch die expression Alternativen anwendbar sind, muss die erste gewählt werden. (§15.6.1)
Beispiel: Es gibt zwei häufige Anwendungsfälle für lokale Funktionen: Iteratormethoden und asynchrone Methoden. Bei Iteratormethoden werden Ausnahmen nur festgestellt, wenn Code aufgerufen wird, der die zurückgegebene Sequenz auflistet. In asynchronen Methoden werden Ausnahmen nur erkannt, wenn auf die zurückgegebene Task gewartet wird. Im folgenden Beispiel wird veranschaulicht, wie mithilfe einer lokalen Funktion die Überprüfung der Parameter von der Iteratorimplementierung getrennt wird:
public static IEnumerable<char> AlphabetSubset(char start, char end) { if (start < 'a' || start > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter"); } if (end < 'a' || end > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); } if (end <= start) { throw new ArgumentException( $"{nameof(end)} must be greater than {nameof(start)}"); } return AlphabetSubsetImplementation(); IEnumerable<char> AlphabetSubsetImplementation() { for (var c = start; c < end; c++) { yield return c; } } }Endbeispiel
Sofern nicht anders unten angegeben, ist die Semantik aller Grammatikelemente identisch mit method_declaration (§15.6.1), die im Kontext einer lokalen Funktion anstelle einer Methode gelesen wird.
Der Bezeichner einer lokalen Funktionsdeklaration muss in seinem deklarierten Blockbereich eindeutig sein, einschließlich aller umschließenden lokalen Variablendeklarationsräume. Eine Folge davon ist, dass überladene local_function_declarations nicht zulässig sind.
Ein local_function_declaration kann einen async (§15.14)-Modifizierer und einen unsafe (§24.1)-Modifizierer enthalten. Wenn die Deklaration den async Modifizierer enthält, muss der Rückgabetyp void oder ein «TaskType» Typ sein (§15.14.1). Wenn die Deklaration den static Modifizierer enthält, handelt es sich bei der Funktion um eine statische lokale Funktion. Andernfalls handelt es sich um eine nicht statische lokale Funktion. Es ist ein Kompilierfehler, wenn type_parameter_list oder parameter_listattributes enthalten. Wenn die lokale Funktion in einem unsicheren Kontext (§24.2) deklariert wird, kann die lokale Funktion unsicheren Code enthalten, auch wenn die lokale Funktionsdeklaration den Modifizierer nicht enthält unsafe .
Eine lokale Funktion wird im Bereich der Blockierung deklariert. Eine nicht-statische lokale Funktion kann Variablen aus dem einschließenden Bereich erfassen, während eine statische lokale Funktion dies nicht darf (sie hat also keinen Zugriff auf einschließende lokale Funktionen, Parameter, nicht-statische lokale Funktionen oder this). Es handelt sich um einen Kompilierungsfehler, wenn eine erfasste Variable vom Textkörper einer nicht statischen lokalen Funktion gelesen wird, aber nicht unbedingt vor jedem Aufruf der Funktion zugewiesen wird. Ein Compiler muss bestimmen, welche Variablen definitiv bei der Rückgabe zugewiesen werden (§9.4.4.33).
Wenn der Typ eines this ein Strukturtyp ist, ist es ein Kompilierungszeitfehler, wenn der Rumpf einer lokalen Funktion auf this zugreift. Dies gilt unabhängig davon, ob der Zugriff explizit (wie in this.x) oder implizit (wie in x, wobei x ein Instanzmitglied der Struct ist) erfolgt. Diese Regel verbietet nur diesen Zugriff und wirkt sich nicht darauf aus, ob die Elementsuche zu einem Mitglied der Struktur führt.
Es ist ein Kompilierungszeitfehler, wenn der Textkörper der lokalen Funktion eine goto-Anweisung, eine break-Anweisung oder eine continue-Anweisung enthält, deren Ziel außerhalb des Textkörpers der lokalen Funktion liegt.
Hinweis: die oben genannten Regeln für
thisgotoanonyme Funktionen in §12.21.3. Hinweisende
Eine lokale Funktion kann vor der Deklaration von einem lexikalischen Punkt aufgerufen werden. Es ist jedoch ein Kompilierfehler, wenn die Funktion lexikalisch vor der Deklaration einer in der lokalen Funktion verwendeten Variable deklariert wird (§7.7).
Es ist ein Kompilierungsfehler, wenn eine lokale Funktion einen Parameter, Typparameter oder eine lokale Variable mit demselben Namen deklariert wie eine, die in einem umgebenden lokalen Variablenbereich deklariert wurde.
Lokale Funktionskörper sind immer erreichbar. Der Endpunkt einer lokalen Funktionsdeklaration ist erreichbar, wenn der Anfangspunkt der lokalen Funktionsdeklaration erreichbar ist.
Beispiel: Im folgenden Beispiel ist der Textkörper
Lerreichbar, obwohl der AnfangspunktLnicht erreichbar ist. Da der AnfangspunktLnicht erreichbar ist, ist die Anweisung nach dem EndpunktLnicht erreichbar:class C { int M() { L(); return 1; // Beginning of L is not reachable int L() { // The body of L is reachable return 2; } // Not reachable, because beginning point of L is not reachable return 3; } }Anders ausgedrückt: Die Position einer lokalen Funktionsdeklaration wirkt sich nicht auf die Reichweite von Anweisungen in der enthaltenden Funktion aus. Endbeispiel
Wenn der Typ des Arguments für eine lokale Funktion lautet dynamic, wird die aufgerufene Funktion zur Kompilierungszeit und nicht zur Laufzeit aufgelöst.
Eine lokale Funktion darf nicht in einem Ausdrucksbaum verwendet werden.
Eine statische lokale Funktion
- Kann auf statische Elemente, Typparameter, Konstantendefinitionen und statische lokale Funktionen aus dem eingeschlossenen Bereich verweisen.
- Sie darf weder auf
thisoderbasenoch auf Instanzmitglieder aus einer implizitenthis-Referenz, noch auf lokale Variablen, Parameter oder nicht-statische lokale Funktionen aus dem einschließenden Bereich verweisen. Alle diese sind jedoch in einemnameof()Ausdruck zulässig.
13.7 Ausdrücke als Anweisungen
Ein expression_statement wertet einen bestimmten Ausdruck aus. Der vom Ausdruck berechnete Wert (falls vorhanden) wird verworfen.
expression_statement
: statement_expression ';'
;
statement_expression
: null_conditional_invocation_expression
| invocation_expression
| object_creation_expression
| assignment
| post_increment_expression
| post_decrement_expression
| pre_increment_expression
| pre_decrement_expression
| await_expression
;
Nicht alle Ausdrücke sind als Anweisungen zulässig.
Hinweis: Insbesondere Ausdrücke wie
x + yundx == 1, die lediglich einen Wert berechnen (der verworfen wird), sind nicht als Anweisungen zulässig. Hinweisende
Die Ausführung eines expression_statement wertet den enthaltenen Ausdruck aus und überträgt dann die Steuerung an den Endpunkt der expression_statement. Der Endpunkt einer expression_statement ist erreichbar, wenn diese expression_statement erreichbar ist.
13.8 Auswahl-Anweisungen
13.8.1 Allgemein
Auswahlanweisungen wählen auf der Grundlage des Wertes eines Ausdrucks eine von mehreren möglichen Anweisungen zur Ausführung aus.
selection_statement
: if_statement
| switch_statement
;
13.8.2 Die If-Anweisung
Die if Anweisung wählt eine Anweisung für die Ausführung basierend auf dem Wert eines booleschen Ausdrucks aus.
if_statement
: 'if' '(' boolean_expression ')' embedded_statement
| 'if' '(' boolean_expression ')' embedded_statement
'else' embedded_statement
;
Ein else-Element ist mit dem lexikalisch nächstgelegenen if verknüpft, das von der Syntax her möglich ist.
Beispiel: So kann eine
if-Anweisung der Formif (x) if (y) F(); else G();entspricht
if (x) { if (y) { F(); } else { G(); } }Endbeispiel
Eine if Anweisung wird wie folgt ausgeführt:
- Die boolean_expression (§12.26) wird ausgewertet.
- Wenn der boolesche Ausdruck ergibt
true, wird das Steuerelement an die erste eingebettete Anweisung übertragen. Wenn und wenn das Steuerelement den Endpunkt dieser Anweisung erreicht, wird die Steuerung an den Endpunkt derifAnweisung übertragen. - Wenn der boolesche Ausdruck den Wert
falseergibt und einelseElement vorhanden ist, wird die Kontrolle auf die zweite eingebettete Anweisung übertragen. Wenn und wenn das Steuerelement den Endpunkt dieser Anweisung erreicht, wird die Steuerung an den Endpunkt derifAnweisung übertragen. - Wenn der boolesche Ausdruck
falseergibt und einelse-Teil nicht vorhanden ist, wird die Kontrolle an den Endpunkt derif-Anweisung übertragen.
Die erste eingebettete Anweisung einer if Anweisung ist erreichbar, wenn die if Anweisung erreichbar ist und der boolesche Ausdruck nicht den Konstantenwert falseaufweist.
Die zweite eingebettete Anweisung einer if Anweisung, falls vorhanden, ist erreichbar, wenn die if Anweisung erreichbar ist und der boolesche Ausdruck nicht den Konstantenwert trueaufweist.
Der Endpunkt einer if Anweisung ist erreichbar, wenn der Endpunkt mindestens einer der eingebetteten Anweisungen erreichbar ist. Darüber hinaus ist der Endpunkt einer if Anweisung ohne else Teil erreichbar, wenn die if Anweisung erreichbar ist und der boolesche Ausdruck nicht den Konstantenwert trueaufweist.
13.8.3 Die Switch-Anweisung
Die switch Anweisung wählt für die Ausführung einer Anweisungsliste mit einer zugeordneten Switchbezeichnung aus, die dem Wert der selector_expression des Schalters entspricht.
switch_statement
: 'switch' selector_expression switch_block
;
selector_expression
: '(' expression ')'
| tuple_expression
;
switch_block
: '{' switch_section* '}'
;
switch_section
: switch_label+ statement_list
;
switch_label
: 'case' pattern case_guard? ':'
| 'default' ':'
;
case_guard
: 'when' null_coalescing_expression
;
Ein switch_statement besteht aus dem Schlüsselwort switch, gefolgt von einem tuple_expression oder einem Klammerausdruck (jeder wird als selector_expression bezeichnet), gefolgt von einem switch_block. Der switch_block besteht aus null oder mehr switch_sections, eingeschlossen in geschweiften Klammern. Jede switch_section besteht aus einem oder mehreren switch_labelgefolgt von einem statement_list (§13.3.2). Jede switch_label enthält case ein zugeordnetes Muster (§11), mit dem der Wert der selector_expression des Schalters getestet wird. Wenn case_guard vorhanden ist, muss ihr Ausdruck implizit in den Typ bool konvertierbar sein, und dieser Ausdruck wird als zusätzliche Bedingung ausgewertet, damit der Fall als erfüllt angesehen wird.
Hinweis: Aus Gründen der Einfachheit können die Klammern in switch_statement weggelassen werden, wenn die selector_expression ein tuple_expression ist. So kann z. B.
switch ((a, b)) …alsswitch (a, b) …geschrieben werden. Hinweisende
Der Steuerungstyp einer switch Anweisung wird durch die selector_expression der Switch festgelegt.
- Wenn der Typ der selector_expression des Schalters lautet
sbyte, ,byte,short,ushort,int,uint, ,long,boolcharstringulongoder ein enum_type, oder wenn es sich um den Nullwerttyp handelt, der einem dieser Typen entspricht, dann ist dies der steuerungsfähige Typ derswitchAnweisung. - Andernfalls ist genau eine benutzerdefinierte implizite Konvertierung vom Typ der selector_expression des Switchs in einen der folgenden möglichen Regeltypen vorhanden:
sbyte,byte,shortintushort,uintulongcharlongstringoder, ein Nullwerttyp, der einem dieser Typen entspricht, dann ist der konvertierte Typ der Anweisung der Regeltyp.switch - Andernfalls ist der Steuerungstyp der
switchAnweisung der selector_expression des Schalters. Es handelt sich um einen Fehler, wenn kein solcher Typ vorhanden ist.
In einer default-Anweisung darf höchstens eine switch Kennzeichnung vorkommen.
Es ist ein Fehler, wenn das Muster einer Switchbezeichnung nicht anwendbar ist (§11.2.1) auf den Typ des Eingabeausdrucks.
Es ist ein Fehler, wenn das Muster einer beliebigen switch-Kennzeichnung subsumiert wird durch (§11.3) das Set der Muster früherer switch-Kennzeichnungen der switch-Anweisung, die keinen Case Guard haben oder deren Case Guard ein konstanter Ausdruck mit dem Wert true ist.
Beispiel:
switch (shape) { case var x: break; case var _: // error: pattern subsumed, as previous case always matches break; default: break; // warning: unreachable, all possible values already handled. }Endbeispiel
Eine switch Anweisung wird wie folgt ausgeführt:
- Die selector_expression der Option wird ausgewertet und in den Steuerungstyp konvertiert.
- Die Steuerung wird gemäß dem Wert der selector_expression des konvertierten Schalters übertragen:
- Das lexikalische erste Muster in der Gruppe von
caseBezeichnungen in derselbenswitchAnweisung, die dem Wert der selector_expression des Schalters entspricht und für das der Guard-Ausdruck entweder nicht vorhanden ist oder als wahr ausgewertet wird, bewirkt, dass das Steuerelement auf die Anweisungsliste nach der übereinstimmendencaseBezeichnung übertragen wird. - Andernfalls, wenn eine
defaultKennzeichnung vorhanden ist, wird die Kontrolle auf die Anweisungsliste übertragen, die auf diedefaultKennzeichnung folgt. - Andernfalls wird die Steuerung an den Endpunkt der
switchAnweisung übertragen.
- Das lexikalische erste Muster in der Gruppe von
Hinweis: Die Reihenfolge, in der Muster zur Laufzeit abgeglichen werden, ist nicht definiert. Ein Compiler darf (muss aber nicht) Muster außerhalb der Reihenfolge abgleichen und die Ergebnisse bereits abgeglichener Muster wiederverwenden, um das Ergebnis des Abgleichs mit anderen Mustern zu berechnen. Dennoch ist ein Compiler erforderlich, um das lexikalisch erste Muster zu ermitteln, das mit dem Ausdruck übereinstimmt und für das die Guard-Klausel entweder nicht vorhanden ist oder zu
trueausgewertet wird. Hinweisende
Wenn der Endpunkt der Anweisungsliste eines Switchabschnitts erreichbar ist, tritt ein Kompilierungszeitfehler auf. Dies wird als „no fall through“-Regel bezeichnet.
Beispiel: Das Beispiel
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; default: CaseOthers(); break; }ist gültig, da kein Schalterabschnitt einen erreichbaren Endpunkt aufweist. Im Gegensatz zu C und C++ darf die Ausführung eines switch-Abschnitts nicht zum nächsten switch-Abschnitt „durchfallen“, und das Beispiel
switch (i) { case 0: CaseZero(); case 1: CaseZeroOrOne(); default: CaseAny(); }führt zu einem Kompilierungszeitfehler. Wenn die Ausführung eines Schalterabschnitts auf die Ausführung eines anderen Schalterabschnitts folgt, muss eine explizite
goto caseodergoto defaulterklärung verwendet werden:switch (i) { case 0: CaseZero(); goto case 1; case 1: CaseZeroOrOne(); goto default; default: CaseAny(); break; }Endbeispiel
In einer switch_section sind mehrere Labels zulässig.
Beispiel: Das Beispiel
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; case 2: default: CaseTwo(); break; }ist gültig. Das Beispiel verstößt nicht gegen die Regel "kein Durchfallen", da die Markierungen
case 2:unddefault:Teil derselben switch_section sind.Endbeispiel
Hinweis: Die "Kein-Durchfallen"-Regel verhindert eine häufige Fehlerklasse, die in C und C++ auftritt, wenn
breakAnweisungen versehentlich weggelassen werden. Zum Beispiel können die Abschnitte der obigenswitch-Anweisung umgekehrt werden, ohne das Verhalten der Anweisung zu beeinflussen:switch (i) { default: CaseAny(); break; case 1: CaseZeroOrOne(); goto default; case 0: CaseZero(); goto case 1; }Hinweisende
Hinweis: Die Anweisungsliste eines switch-Abschnitts endet typischerweise mit einer
break,goto caseodergoto defaultAnweisung, aber jedes Konstrukt, das den Endpunkt der Anweisungsliste unerreichbar macht, ist erlaubt. Beispielsweise ist einewhileAnweisung, die vom booleschen Ausdrucktruegesteuert wird, bekannt, dass sie nie ihren Endpunkt erreicht. Ebenso überträgt einethrowoderreturnAnweisung immer die Kontrolle an anderer Stelle und erreicht nie ihren Endpunkt. Daher ist das folgende Beispiel gültig:switch (i) { case 0: while (true) { F(); } case 1: throw new ArgumentException(); case 2: return; }Hinweisende
Beispiel: Der Regeltyp einer
switchAnweisung kann der Typstringsein. Zum Beispiel:void DoCommand(string command) { switch (command.ToLower()) { case "run": DoRun(); break; case "save": DoSave(); break; case "quit": DoQuit(); break; default: InvalidCommand(command); break; } }Endbeispiel
Hinweis: Wie bei den Zeichenfolgengleichheitsoperatoren (§12.14.8) wird bei der Anweisung die
switchGroß-/Kleinschreibung beachtet und nur dann ein bestimmter Switch-Abschnitt ausgeführt, wenn die selector_expression Zeichenfolge genau mit einercaseBezeichnungskonstante übereinstimmt. Hinweisende
Wenn der Regeltyp einer switch Anweisung oder ein Nullwerttyp ist string , ist der Wert null als case Bezeichnungskonstante zulässig.
Die statement_list eines switch_block kann Deklarationsanweisungen enthalten (§13.6). Der Bereich einer lokalen Variablen oder Konstante, die in einem Switchblock deklariert ist, ist der Switchblock.
Eine Schalterbezeichnung ist erreichbar, wenn mindestens eine der folgenden Bedingungen zutrifft:
- Die selector_expression des Schalters ist ein konstanter Wert und eine der beiden
- Die Kennzeichnung ist ein
case, dessen Muster mit (§11.2.1) diesem Wert übereinstimmen würde, und der guard von Kennzeichnung ist entweder nicht vorhanden oder kein konstanter Ausdruck mit dem Wert false; oder - Es handelt sich um eine
defaultKennzeichnung, und kein Switch-Abschnitt enthält eine Case-Kennzeichnung, deren Muster mit diesem Wert übereinstimmen würde und deren Guard entweder nicht vorhanden oder ein konstanter Ausdruck mit dem Wert true ist.
- Die Kennzeichnung ist ein
- Die selector_expression des Schalters ist kein konstanter Wert und beides
- Die Kennzeichnung ist ein
caseohne Guard oder mit einem Guard, dessen Wert nicht die Konstante false ist; oder - Es ist eine
defaultKennzeichnung und- das Set der Muster, die unter den Fällen der Switch-Anweisung auftreten, die keine Guards haben oder Guards haben, deren Wert die Konstante true ist, ist nicht vollständig (§11.4) für den Switch-Regulationstyp; oder
- der switch governing Typ ist ein nullable Typ und das Set der Muster, die in den Fällen der switch-Anweisung vorkommen, die keine guard haben oder die guard haben, deren Wert die Konstante true ist, enthält kein Muster, das auf den Wert
nullpassen würde.
- Die Kennzeichnung ist ein
- Die Kennzeichnung switch wird durch eine erreichbare
goto caseodergoto defaultAnweisung referenziert.
Die Anweisungsliste eines bestimmten Switchabschnitts ist erreichbar, wenn die switch Anweisung erreichbar ist und der Switch-Abschnitt eine erreichbare Switchbezeichnung enthält.
Der Endpunkt einer switch Anweisung ist erreichbar, wenn die Switch-Anweisung erreichbar ist und mindestens eine der folgenden Werte zutrifft:
- Die
switchAnweisung enthält eine erreichbarebreakAnweisung, die dieswitchAnweisung verlässt. - Es ist keine
defaultKennzeichnung vorhanden und entweder- Die selector_expression des Schalters ist ein nicht konstanter Wert, und der Satz von Mustern, die in den Fällen der Switch-Anweisung angezeigt werden, die keine Schutzvorrichtungen oder Wächter aufweisen, deren Wert die Konstante "true" ist, ist nicht erschöpfend (§11.4) für den Switch-Steuerungstyp.
- Die selector_expression des Schalters ist ein nicht konstanter Wert eines nullfähigen Typs und kein Muster, das zwischen den Fällen der Switch-Anweisung angezeigt wird, die keine Schutzvorrichtungen aufweist oder über Wächter verfügt, deren Wert die Konstante "true" ist, entspricht dem Wert
null. - Die selector_expression des Schalters ist ein konstanter Wert und keine
caseBeschriftung ohne Schutz oder deren Schutz die Konstante "true" ist, würde mit diesem Wert übereinstimmen.
Beispiel: Der folgende Code zeigt eine prägnante Verwendung der
whenKlausel:static object CreateShape(string shapeDescription) { switch (shapeDescription) { case "circle": return new Circle(2); … case var o when string.IsNullOrWhiteSpace(o): return null; default: return "invalid shape description"; } }Der Var-Fall stimmt mit
nullder leeren Zeichenfolge oder einer beliebigen Zeichenfolge überein, die nur Leerzeichen enthält. Endbeispiel
13.9 Iterationsanweisungen
13.9.1 Allgemein
Iterationsanweisungen führen eine eingebettete Anweisung wiederholt aus.
iteration_statement
: while_statement
| do_statement
| for_statement
| foreach_statement
;
13.9.2 Die While-Anweisung
Die while Anweisung führt abhängig von der Bedingung eine eingebettete Anweisung null- oder mehrmals aus.
while_statement
: 'while' '(' boolean_expression ')' embedded_statement
;
Eine while Anweisung wird wie folgt ausgeführt:
- Die boolean_expression (§12.26) wird ausgewertet.
- Wenn der boolesche Ausdruck ergibt
true, wird das Steuerelement an die eingebettete Anweisung übertragen. Wenn und wenn das Steuerelement den Endpunkt der eingebetteten Anweisung erreicht (möglicherweise von der Ausführung einercontinueAnweisung), wird das Steuerelement an den Anfang derwhileAnweisung übertragen. - Wenn der boolesche Ausdruck ergibt
false, wird das Steuerelement an den Endpunkt derwhileAnweisung übertragen.
Innerhalb der eingebetteten Anweisung einer while Anweisung kann eine break Anweisung (§13.10.2) verwendet werden, um die Steuerung an den Endpunkt der while Anweisung zu übertragen (damit die Iteration der eingebetteten Anweisung endet), und eine continue Anweisung (§13.10.3) kann verwendet werden, um die Steuerung an den Endpunkt der eingebetteten Anweisung zu übertragen (wodurch eine weitere Iteration der while Anweisung ausgeführt wird).
Die eingebettete Anweisung einer while Anweisung kann erreicht werden, wenn die while Anweisung erreichbar ist und der boolesche Ausdruck nicht den Konstantenwert falseaufweist.
Der Endpunkt einer while Anweisung ist erreichbar, wenn mindestens einer der folgenden Werte zutrifft:
- Die
whileAnweisung enthält eine erreichbarebreakAnweisung, die diewhileAnweisung verlässt. - Die
whileAnweisung ist erreichbar, und der boolesche Ausdruck hat nicht den Konstantenwerttrue.
13.9.3 Die Do-Anweisung
Die do Anweisung führt eine eingebettete Anweisung bedingt ein- oder mehrmals aus.
do_statement
: 'do' embedded_statement 'while' '(' boolean_expression ')' ';'
;
Eine do Anweisung wird wie folgt ausgeführt:
- Die Steuerung wird an die eingebettete Anweisung übertragen.
- Wenn und wenn die Steuerung den Endpunkt der eingebetteten Anweisung (möglicherweise aus ausführung einer
continueAnweisung) erreicht, wird die boolean_expression (§12.26) ausgewertet. Wenn der boolesche Ausdruck ergibttrue, wird das Steuerelement an den Anfang derdoAnweisung übertragen. Andernfalls wird die Steuerung an den Endpunkt derdoAnweisung übertragen.
Innerhalb der eingebetteten Anweisung einer do Anweisung kann eine break Anweisung (§13.10.2) verwendet werden, um die Steuerung an den Endpunkt der do Anweisung zu übertragen (damit die Iteration der eingebetteten Anweisung endet), und eine continue Anweisung (§13.10.3) kann verwendet werden, um die Steuerung an den Endpunkt der eingebetteten Anweisung zu übertragen (wodurch eine weitere Iteration der do Anweisung ausgeführt wird).
Die eingebettete Anweisung einer do-Anweisung ist erreichbar, wenn die do-Anweisung erreichbar ist.
Der Endpunkt einer do Anweisung ist erreichbar, wenn mindestens einer der folgenden Werte zutrifft:
- Die
doAnweisung enthält eine erreichbarebreakAnweisung, die diedoAnweisung verlässt. - Der Endpunkt der eingebetteten Anweisung ist erreichbar, und der boolesche Ausdruck hat nicht den Konstantenwert
true.
13.9.4 Die For-Anweisung
Die for Anweisung wertet eine Abfolge von Initialisierungsausdrücken aus und führt dann, während eine Bedingung wahr ist, wiederholt eine eingebettete Anweisung aus und wertet eine Abfolge von Iterationsausdrücken aus.
for_statement
: 'for' '(' for_initializer? ';' for_condition? ';' for_iterator? ')'
embedded_statement
;
for_initializer
: local_variable_declaration
| statement_expression_list
;
for_condition
: boolean_expression
;
for_iterator
: statement_expression_list
;
statement_expression_list
: statement_expression (',' statement_expression)*
;
Die for_initializer besteht, sofern vorhanden, entweder aus einem local_variable_declaration (§13.6.2) oder einer Liste von statement_expression(§13.7) getrennt durch Kommas. Der Bereich einer lokalen Variablen, die von einem for_initializer deklariert wird, ist die for_initializer, for_condition, for_iterator und embedded_statement.
Die for_condition, sofern vorhanden, ist eine boolean_expression (§12.26).
Die for_iterator besteht, sofern vorhanden, aus einer Liste von statement_expressions (§13.7), die durch Kommas getrennt sind.
Eine for Anweisung wird wie folgt ausgeführt:
- Wenn ein for_initializer vorhanden ist, werden die Variableninitialisierer oder Anweisungsausdrücke in der Reihenfolge ausgeführt, in der sie geschrieben werden. Dieser Schritt wird nur einmal ausgeführt.
- Wenn ein for_condition vorhanden ist, wird es ausgewertet.
- Wenn die for_condition nicht vorhanden ist oder wenn die Auswertung
trueergibt, wird die Kontrolle an die eingebettete Anweisung übertragen. Wenn und wenn das Steuerelement den Endpunkt der eingebetteten Anweisung (möglicherweise von der Ausführung einercontinueAnweisung) erreicht, werden die Ausdrücke des for_iterator (falls vorhanden) in Sequenz ausgewertet, und dann wird eine weitere Iteration durchgeführt, beginnend mit der Auswertung der for_condition im obigen Schritt. - Wenn die for_condition vorhanden ist und die Auswertung ergibt
false, wird die Kontrolle an den Endpunkt derforAnweisung übertragen.
Innerhalb der eingebetteten Anweisung einer for-Anweisung kann eine break-Anweisung (§13.10.2) verwendet werden, um die Kontrolle an den Endpunkt der for-Anweisung zu übertragen (und damit die Iteration der eingebetteten Anweisung zu beenden), und eine continue-Anweisung (§13. 10.3) kann verwendet werden, um die Kontrolle an den Endpunkt der eingebetteten Anweisung zu übertragen (und damit den for_iterator auszuführen und eine weitere Iteration der for-Anweisung durchzuführen, beginnend mit der for_condition).
Die eingebettete Anweisung einer for-Anweisung ist erreichbar, wenn eine der folgenden Bedingungen erfüllt ist:
- Die
forAnweisung ist erreichbar und es ist keine for_condition vorhanden. - Die
forAnweisung ist erreichbar, und es ist eine for_condition vorhanden, die nicht den konstanten Wertfalseaufweist.
Der Endpunkt einer for Anweisung ist erreichbar, wenn mindestens einer der folgenden Werte zutrifft:
- Die
forAnweisung enthält eine erreichbarebreakAnweisung, die dieforAnweisung verlässt. - Die
forAnweisung ist erreichbar, und es ist eine for_condition vorhanden, die nicht den konstanten Werttrueaufweist.
13.9.5 Die Foreach-Anweisung
13.9.5.1 Allgemein
Die foreach Anweisung listet die Elemente einer Auflistung auf und führt eine eingebettete Anweisung für jedes Element der Auflistung aus.
foreach_statement
: 'await'? 'foreach' '(' ref_kind? local_variable_type identifier
'in' expression ')' embedded_statement
;
Die local_variable_type und der Bezeichner einer Foreach-Anweisung deklarieren die Iterationsvariable der Anweisung. Wenn der var Bezeichner als local_variable_type angegeben wird und kein benannter var Typ im Bereich enthalten ist, wird die Iterationsvariable als implizit typierte Iterationsvariable bezeichnet, und der Typ wird als Elementtyp der foreach Anweisung verwendet, wie unten angegeben.
Es stellt einen Kompilierungsfehler dar, wenn sowohl await als auch ref_kind in einem foreach statement vorhanden sind.
Wenn foreach_statement beide oder keine ref und readonly enthält, bezeichnet die Iterationsvariable eine Variable, die als schreibgeschützt behandelt wird. Andernfalls, wenn foreach_statementref ohne readonly enthält, bezeichnet die Iterationsvariable eine Variable, die beschreibbar sein soll.
Die Iterationsvariable entspricht einer lokalen Variablen mit einem Bereich, der sich über die eingebettete Anweisung erstreckt. Während der Ausführung einer foreach Anweisung stellt die Iterationsvariable das Auflistungselement dar, für das derzeit eine Iteration ausgeführt wird. Wenn die Iterationsvariable eine schreibgeschützte Variable bezeichnet, tritt ein Kompilierfehler auf, wenn die eingebettete Anweisung versucht, sie zu ändern (durch Zuweisung oder die ++- und ---Operatoren) oder sie als Referenz oder Ausgabeparameter zu übergeben.
Die Kompilierungszeitverarbeitung einer foreach Anweisung bestimmt zuerst den Auflistungstyp (C), den Enumerationstyp (E) und den Iterationstyp (Tref Toder ref readonly T) des Ausdrucks.
Die Ermittlung ist für die synchronen und asynchronen Versionen ähnlich. Verschiedene Schnittstellen mit unterschiedlichen Methoden und Rückgabetypen unterscheiden die synchronen und asynchronen Versionen. Der allgemeine Vorgang wird wie folgt fortgesetzt. Namen innerhalb von '«' und '»' sind Platzhalter für die tatsächlichen Namen für synchrone und asynchrone Iteratoren. Die für «GetEnumerator», «MoveNext», «IEnumerable»T, «IEnumerator»<T>, und alle anderen Unterscheidungen sind in < für eine synchrone > Anweisung und in §13.9.5.3 für eine asynchrone foreach Anweisung detailliert beschrieben.foreach
- Bestimmen Sie, ob der
Xüber eine geeignete «GetEnumerator»-Methode verfügt:- Führen Sie die Elementsuche für den Typ
Xmit dem Bezeichner «GetEnumerator» und ohne Typargumente durch. Wenn die Elementsuche keine Übereinstimmung erzeugt oder eine Mehrdeutigkeit erzeugt oder eine Übereinstimmung erzeugt, die keine Methodengruppe ist, suchen Sie nach einer aufzählbaren Schnittstelle, wie in Schritt 2 beschrieben. Es wird empfohlen, eine Warnung auszugeben, wenn die Mitgliedersuche etwas anderes als eine Methodengruppe oder keine Übereinstimmung ergibt. - Führen Sie die Überladungsauflösung mithilfe der resultierenden Methodengruppe und einer leeren Argumentliste aus. Führt die Überladungsauflösung zu keiner anwendbaren Methode, führt zu einer Mehrdeutigkeit oder führt zu einer einzigen besten Methode, aber diese Methode ist entweder statisch oder nicht öffentlich, überprüfen Sie, wie unten beschrieben, auf eine aufzählbare Schnittstelle. Es wird empfohlen, dass eine Warnung ausgegeben wird, wenn die Überladungsauflösung nichts anderes als eine eindeutige öffentliche Instanzmethode oder keine anwendbaren Methoden erzeugt.
- Wenn der Rückgabetyp
Eder «GetEnumerator»-Methode keine Klasse, Struktur oder Schnittstellentyp ist, erzeugen Sie einen Fehler, und führen Sie keine weiteren Schritte aus. - Führen Sie die Elementsuche
Emit dem BezeichnerCurrentund keine Typargumente aus. Wenn die Membersuche keine Übereinstimmung erzeugt, ist das Ergebnis ein Fehler, oder das Ergebnis ist alles außer einer öffentlichen Instanzeigenschaft, die das Lesen zulässt, einen Fehler erzeugen und keine weiteren Schritte ausführen. - Führen Sie die Elementsuche
Emit dem Bezeichner «MoveNext» und ohne Typargumente durch. Wenn die Elementsuche keine Übereinstimmung erzeugt, ist das Ergebnis ein Fehler, oder das Ergebnis ist alles außer einer Methodengruppe, erzeugt einen Fehler und führt keine weiteren Schritte aus. - Führen Sie die Überladungsauflösung für die Methodengruppe mit einer leeren Argumentliste aus. Wenn die Überladungsauflösung zu folgenden Ergebnissen führt: keine anwendbaren Methoden; mehrdeutig; oder eine einzige beste Methode, aber diese Methode ist entweder statisch oder nicht öffentlich, oder der Rückgabetyp ist kein zulässiger Rückgabetyp; dann wird ein Fehler erzeugt und keine weiteren Schritte ausgeführt.
- Der Auflistungstyp ist
X, der Enumerationstyp istE, und der Iterationstyp ist der Typ derCurrentEigenschaft.
- Führen Sie die Elementsuche für den Typ
- Suchen Sie andernfalls nach einer aufzählbaren Schnittstelle:
- Wenn unter allen Typen
Tᵢ, für die eine implizite Konvertierung vonX«IEnumerable»<Ti> vorhanden ist, gibt es einen eindeutigen TypT,Tder nichtdynamicund für alle anderenTᵢgibt es eine implizite Konvertierung von «IEnumerable»<T> zu «IEnumerable»<Ti>, dann ist der Sammlungstyp die Schnittstelle «IEnumerable»<T>, der Enumerationstyp ist die Schnittstelle «IEnumerator»<T>, und der Iterationstyp lautetT. - Andernfalls wird bei mehr als einem solchen Typ
Tein Fehler erzeugt und keine weiteren Schritte ausgeführt.
- Wenn unter allen Typen
Anmerkung: Wenn Ausdruck den Wert
nullhat, wird zur Laufzeit einSystem.NullReferenceExceptionausgelöst. Hinweisende
Eine Implementierung darf eine bestimmte foreach_statement anders implementieren; z. B. aus Leistungsgründen, sofern das Verhalten mit den in §13.9.5.2 und §13.9.5.3 beschriebenen Erweiterungen übereinstimmt.
13.9.5.2 Synchrone Foreach
Ein synchrones foreach Schlüsselwort schließt das await Schlüsselwort vor dem foreach Schlüsselwort nicht ein. Die Bestimmung des Sammlungstyps, des Enumerationstyps und des Iterationstyps erfolgt wie in §13.9.5.1 beschrieben, wobei:
- «GetEnumerator» ist eine
GetEnumeratorMethode. - «MoveNext» ist eine
MoveNextMethode mit einemboolRückgabetyp. - «IEnumerable»<T> ist die
System.Collections.Generic.IEnumerable<T>Schnittstelle. - «IEnumerator»<T> ist die
System.Collections.Generic.IEnumerator<T>Schnittstelle.
Darüber hinaus werden die folgenden Änderungen an den Schritten in §13.9.5.1 vorgenommen:
Vor dem in §13.9.5.1 beschriebenen Prozess werden die folgenden Schritte ausgeführt:
- Wenn es sich bei dem
XAusdruckstyp um einen Arraytyp handelt, gibt es eine implizite Verweiskonvertierung vonXder Schnittstelle, beiIEnumerable<T>derTes sich um den Elementtyp des ArraysX(§17.2.3) handelt. - Wenn der Typ
Xvon Ausdruckdynamicist, dann gibt es eine implizite Konvertierung von Ausdruck in dieIEnumerableSchnittstelle (§10.2.10). Der Sammlungstyp ist dieIEnumerableSchnittstelle, und der Enumerationstyp ist dieIEnumeratorSchnittstelle. Wenn dervarBezeichner als local_variable_type angegeben wird, ist der Iterationstypdynamic. Andernfalls ist erobject.
Wenn der Prozess in §13.9.5.1 abgeschlossen ist, ohne einen einzelnen Sammlungstyp, Enumeratortyp und Iterationstyp zu erstellen, werden die folgenden Schritte ausgeführt:
- Wenn es eine implizite Konvertierung von
XderSystem.Collections.IEnumerableSchnittstelle gibt, ist der Sammlungstyp diese Schnittstelle, der Enumerationstyp ist die SchnittstelleSystem.Collections.IEnumerator, und der Iterationstyp istobject. - Andernfalls wird ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt.
Eine foreach-Anweisung der Form
foreach (V v in x) «embedded_statement»
ist dann gleichbedeutend mit:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
V v = (V)(T)e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
Die Variable e ist für den Ausdruck x oder die eingebettete Anweisung oder einen anderen Quellcode des Programms nicht sichtbar oder zugänglich. Die Variable v ist in der eingebetteten Anweisung schreibgeschützt. Wenn keine explizite Konvertierung (§10.3) von T (dem Iterationstyp) in V (die local_variable_type in der foreach Anweisung) vorliegt, wird ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt.
Wenn die Iterationsvariable eine Referenzvariable ist (§9.7), eine foreach-Anweisung der Form
foreach (ref V v in x) «embedded_statement»
ist dann gleichbedeutend mit:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
ref V v = ref e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
Die Variable e ist für den Ausdruck x oder die eingebettete Anweisung oder einen anderen Quellcode des Programms nicht sichtbar oder zugänglich. Die Referenzvariable v ist in der eingebetteten Anweisung schreibgeschützt, darf jedoch v nicht neu zugewiesen werden (§12.23.3). Wenn keine Identitätskonvertierung (§10.2.2) von T (dem Iterationstyp) zu V (dem local_variable_type in der foreach Anweisung) vorhanden ist, wird ein Fehler erzeugt und es werden keine weiteren Schritte ausgeführt.
Eine foreach-Anweisung der Form foreach (ref readonly V v in x) «embedded_statement» hat eine ähnliche Form, aber die Referenzvariable v ist ref readonly in der eingebetteten Anweisung und kann daher nicht neu zugewiesen oder neu zugewiesen werden.
Die Platzierung innerhalb der vwhile Schleife ist wichtig für die Erfassung (§12.21.6.2) durch jede anonyme Funktion, die in der embedded_statement erfolgt.
Beispiel:
int[] values = { 7, 9, 13 }; Action f = null; foreach (var value in values) { if (f == null) { f = () => Console.WriteLine("First value: " + value); } } f();Wenn
vin der erweiterten Form außerhalb derwhileSchleife deklariert würde, würde sie für alle Iterationen freigegeben, und ihr Wert nach derforSchleife wäre der endwert,13was der Aufruf derfSchleife drucken würde. Da jede Iteration über ihre eigene Variablevverfügt, wird die in der ersten Iteration erfasste Variablefweiterhin den Wert7halten, der ausgedruckt wird. (Beachten Sie, dass frühere Versionen von C#vaußerhalb derwhile-Schleife deklarierten.)Endbeispiel
Der Inhalt des finally Blocks wird in den folgenden Schritten gemäß erstellt:
Wenn es eine implizite Konvertierung von
EzurSystem.IDisposableSchnittstelle gibt, dannWenn
Eein nicht-nullbarer Werttyp ist, wird diefinally-Klausel zum semantischen Äquivalent von erweitert:finally { ((System.IDisposable)e).Dispose(); }Andernfalls wird die Klausel
finallyauf das semantische Äquivalent des Folgenden erweitert:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }Wenn
Ejedoch ein Werttyp ist oder ein Typparameter, der zu einem Werttyp instanziiert wurde, verursacht die Konvertierung voneinSystem.IDisposablekein Boxing.
Wenn es sich bei
Eum einen versiegelten Typ handelt, wird diefinally-Klausel andernfalls auf einen leeren Block erweitert.finally {}Andernfalls wird die
finallyKlausel erweitert auf:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }
Die lokale Variable d ist für Benutzercode nicht sichtbar oder zugänglich. Insbesondere steht es nicht im Konflikt mit einer anderen Variablen, deren Bereich den finally Block enthält.
Die Reihenfolge, foreach in der die Elemente eines Arrays durchlaufen werden, lautet wie folgt: Bei eindimensionalen Arrays werden Elemente in zunehmender Indexreihenfolge durchlaufen, beginnend mit Index 0 und enden mit Index Length – 1. Bei mehrdimensionalen Arrays werden die Elemente so durchlaufen, dass zuerst die Indizes der ganz rechten Dimension erhöht werden, dann die der nächsten linken Dimension und so weiter nach links.
Beispiel: Im folgenden Beispiel werden die einzelnen Werte in einer zweidimensionalen Matrix in der Elementreihenfolge gedruckt:
class Test { static void Main() { double[,] values = { {1.2, 2.3, 3.4, 4.5}, {5.6, 6.7, 7.8, 8.9} }; foreach (double elementValue in values) { Console.Write($"{elementValue} "); } Console.WriteLine(); } }Die Ausgabe sieht folgendermaßen aus:
1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9Endbeispiel
Beispiel: Im folgenden Beispiel
int[] numbers = { 1, 3, 5, 7, 9 }; foreach (var n in numbers) { Console.WriteLine(n); }Der Typ von
nwird aufintabgeleitet, der Iterationstyp vonnumbers.Endbeispiel
13.9.5.3 await foreach
Eine asynchrone Foreach verwendet die await foreach Syntax. Die Bestimmung des Sammlungstyps, des Enumerationstyps und des Iterationstyps erfolgt wie in §13.9.5.1 beschrieben, wobei:
- «GetEnumerator» ist eine
GetEnumeratorAsyncMethode, die über einen wartenden Rückgabetyp verfügt (§12.9.9.2). - "MoveNext" ist eine Methode, die über einen
MoveNextAsynczu erwartenden Rückgabetyp (§12.9.9.2) verfügt, wobei der await_expression als einbool(§12.9.9.3) klassifiziert wird. - «IEnumerable»<T> ist die
System.Collections.Generic.IAsyncEnumerable<T>Schnittstelle. - «IEnumerator»<T> ist die
System.Collections.Generic.IAsyncEnumerator<T>Schnittstelle.
Es ist ein Fehler für den Iterationstyp einer await foreach Anweisung, eine Referenzvariable (§9.7) zu sein.
Eine await foreach Anweisung der Form
await foreach (T item in enumerable) «embedded_statement»
ist semantisch gleichbedeutend mit:
var enumerator = enumerable.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
T item = enumerator.Current;
«embedded_statement»
}
}
finally
{
// dispose of enumerator as described later in this clause.
}
Wenn der Ausdruck enumerable einen Methodenaufrufausdruck darstellt und einer der Parameter mit dem EnumeratorCancellationAttribute (§23.5.8) gekennzeichnet ist, wird er CancellationToken an die GetAsyncEnumerator Methode übergeben. Für andere Bibliotheksmethoden ist möglicherweise eine CancellationToken Übergabe erforderlich GetAsyncEnumerator. Wenn diese Methoden Teil des Ausdrucks enumerablesind, müssen die Token in ein einzelnes Token kombiniert werden, als ob von CreateLinkedTokenSource und deren Token Eigenschaft.
Der Inhalt des finally Blocks wird in den folgenden Schritten gemäß erstellt:
Wenn
Eeine barrierefreieDisposeAsync()Methode vorhanden ist, bei der der Rückgabetyp erwartet werden kann (§12.9.9.2), wird diefinallyKlausel auf das semantische Äquivalent von:finally { await e.DisposeAsync(); }Andernfalls wird die
EKlausel auf die semantische Entsprechung erweitert, wenn es eine implizite Konvertierung vonSystem.IAsyncDisposablezu derESchnittstelle gibt undfinallyein nicht nullabler Werttyp ist:finally { await ((System.IAsyncDisposable)e).DisposeAsync(); }Wenn
Ejedoch ein Werttyp ist oder ein Typparameter, der zu einem Werttyp instanziiert wurde, verursacht die Konvertierung voneinSystem.IAsyncDisposablekein Boxing.Andernfalls wird die
EKlausel auf die semantische Entsprechung erweitert, wennref structes sich um einenDispose()Typ handelt und über eine barrierefreiefinallyMethode verfügt:finally { e.Dispose(); }Wenn es sich bei
Eum einen versiegelten Typ handelt, wird diefinally-Klausel andernfalls auf einen leeren Block erweitert.finally {}Andernfalls wird die
finallyKlausel erweitert auf:finally { System.IAsyncDisposable d = e as System.IAsyncDisposable; if (d != null) { await d.DisposeAsync(); } }
Die lokale Variable d ist für Benutzercode nicht sichtbar oder zugänglich. Insbesondere steht es nicht im Konflikt mit einer anderen Variablen, deren Bereich den finally Block enthält.
Hinweis: Es
await foreachist nicht erforderlich, synchron zu verwerfene, wenn kein asynchroner Dispose-Mechanismus verfügbar ist. Hinweisende
13.10 Sprunganweisungen
13.10.1 Allgemein
Sprunganweisungen übertragen bedingungslos die Kontrolle.
jump_statement
: break_statement
| continue_statement
| goto_statement
| return_statement
| throw_statement
;
Die Position, an die eine Sprung-Anweisung die Steuerung überträgt, wird als Ziel der Jump-Anweisung bezeichnet.
Wenn eine Sprung-Anweisung innerhalb eines Blocks auftritt und das Ziel dieser Sprung-Anweisung außerhalb dieses Blocks liegt, wird gesagt, dass die Sprung-Anweisung den Block verlässt. Während eine Sprung-Anweisung die Steuerung aus einem Block übertragen kann, kann sie niemals die Steuerung in einen Block übertragen.
Die Ausführung von Sprunganweisungen wird durch das Vorhandensein von zwischengeschalteten try-Anweisungen erschwert. In Ermangelung solcher try Anweisungen überträgt eine Sprunganweisung bedingungslos die Kontrolle von der Sprunganweisung auf ihr Ziel. In Anwesenheit solcher dazwischenliegenden try Anweisungen ist die Ausführung komplexer. Wenn die Sprunganweisung einen oder mehrere try-Blöcke mit zugehörigen finally-Blöcken verlässt, wird die Kontrolle zunächst an den finally-Block der innersten try-Anweisung übertragen. Wenn und falls die Kontrolle den Endpunkt eines finally-Blocks erreicht, wird die Kontrolle an den finally-Block der nächsten einschließenden try-Anweisung übertragen. Dieser Vorgang wird wiederholt, bis die finally Blöcke aller dazwischen liegenden try Anweisungen ausgeführt wurden.
Beispiel: Im folgenden Code
class Test { static void Main() { while (true) { try { try { Console.WriteLine("Before break"); break; } finally { Console.WriteLine("Innermost finally block"); } } finally { Console.WriteLine("Outermost finally block"); } } Console.WriteLine("After break"); } }werden die
finallyBlöcke, die mit zweitryAnweisungen verbunden sind, ausgeführt, bevor die Kontrolle an das Ziel der Sprunganweisung übertragen wird. Die Ausgabe sieht folgendermaßen aus:Before break Innermost finally block Outermost finally block After breakEndbeispiel
13.10.2 Die Break-Anweisung
Die break-Anweisung verlässt die nächste einschließende switch, while, do, for oder foreach-Anweisung.
break_statement
: 'break' ';'
;
Das Ziel einer break-Anweisung ist der Endpunkt der nächstgelegenen einschließenden switch, while, do, for oder foreach-Anweisung. Wenn eine break Anweisung nicht von einem switch, while, do, for oder foreach Anweisung eingeschlossen ist, tritt ein Kompilierungszeitfehler auf.
Wenn mehrere switch, , while, do, foroder foreach Anweisungen miteinander verschachtelt sind, gilt eine break Anweisung nur für die innerste Anweisung. Um die Steuerung über mehrere verschachtelte Ebenen hinweg zu übertragen, wird eine goto-Anweisung (§13.10.4) verwendet.
Eine break Anweisung kann einen finally Block nicht beenden (§13.11). Wenn eine break Anweisung innerhalb eines finally Blocks auftritt, liegt das Ziel der break Anweisung innerhalb desselben finally Blocks; andernfalls tritt ein Kompilierungsfehler auf.
Eine break Anweisung wird wie folgt ausgeführt:
- Wenn die
break-Anweisung einen oder mehreretry-Blöcke mit zugeordnetenfinally-Blöcken verlässt, wird die Kontrolle zunächst an denfinally-Block der innerstentry-Anweisung übertragen. Wenn und falls die Kontrolle den Endpunkt einesfinally-Blocks erreicht, wird die Kontrolle an denfinally-Block der nächsten einschließendentry-Anweisung übertragen. Dieser Vorgang wird wiederholt, bis diefinallyBlöcke aller dazwischen liegendentryAnweisungen ausgeführt wurden. - Die Steuerung wird an das Ziel der
breakAnweisung übertragen.
Da eine break Aussage die Kontrolle an anderer Stelle bedingungslos überträgt, ist der Endpunkt einer break Aussage nie erreichbar.
13.10.3 Die Continue-Anweisung
Die continue-Anweisung startet eine neue Iteration der nächstgelegenen einschließenden while, do, for oder foreach-Anweisung.
continue_statement
: 'continue' ';'
;
Das Ziel einer continue-Anweisung ist der Endpunkt der eingebetteten Anweisung der nächstgelegenen einschließenden while, do, for oder foreach-Anweisung. Wenn eine continue Anweisung nicht durch eine while, do, for oder foreach Anweisung eingeschlossen ist, tritt ein Kompilierungszeitfehler auf.
Wenn mehrere while, , do, foroder foreach Anweisungen miteinander verschachtelt sind, gilt eine continue Anweisung nur für die innerste Anweisung. Um die Steuerung über mehrere verschachtelte Ebenen hinweg zu übertragen, wird eine goto-Anweisung (§13.10.4) verwendet.
Eine continue Anweisung kann einen finally Block nicht beenden (§13.11). Wenn eine continue Anweisung innerhalb eines finally Blocks auftritt, liegt das Ziel der continue Anweisung innerhalb desselben finally Blocks; andernfalls tritt ein Kompilierungsfehler auf.
Eine continue Anweisung wird wie folgt ausgeführt:
- Wenn die
continue-Anweisung einen oder mehreretry-Blöcke mit zugeordnetenfinally-Blöcken verlässt, wird die Kontrolle zunächst an denfinally-Block der innerstentry-Anweisung übertragen. Wenn und falls die Kontrolle den Endpunkt einesfinally-Blocks erreicht, wird die Kontrolle an denfinally-Block der nächsten einschließendentry-Anweisung übertragen. Dieser Vorgang wird wiederholt, bis diefinallyBlöcke aller dazwischen liegendentryAnweisungen ausgeführt wurden. - Die Steuerung wird an das Ziel der
continueAnweisung übertragen.
Da eine continue Aussage die Kontrolle an anderer Stelle bedingungslos überträgt, ist der Endpunkt einer continue Aussage nie erreichbar.
13.10.4 Die goto-Anweisung
Die goto Anweisung überträgt die Steuerung an eine Anweisung, die durch eine Bezeichnung gekennzeichnet ist.
goto_statement
: 'goto' identifier ';'
| 'goto' 'case' constant_expression ';'
| 'goto' 'default' ';'
;
Das Ziel einer gotoKennzeichnung-Anweisung ist die gekennzeichnete Anweisung mit der angegebenen Kennzeichnung. Wenn eine Bezeichnung mit dem angegebenen Namen nicht im aktuellen Funktionselement vorhanden ist oder sich die goto Anweisung nicht im Bereich der Bezeichnung befindet, tritt ein Kompilierungszeitfehler auf.
Hinweis: Diese Regel ermöglicht die Verwendung einer
gotoAnweisung, die Kontrolle aus einem geschachtelten Bereich, aber nicht in einen geschachtelten Bereich zu übertragen. Im Beispielclass Test { static void Main(string[] args) { string[,] table = { {"Red", "Blue", "Green"}, {"Monday", "Wednesday", "Friday"} }; foreach (string str in args) { int row, colm; for (row = 0; row <= 1; ++row) { for (colm = 0; colm <= 2; ++colm) { if (str == table[row,colm]) { goto done; } } } Console.WriteLine($"{str} not found"); continue; done: Console.WriteLine($"Found {str} at [{row}][{colm}]"); } } }eine
goto-Anweisung wird verwendet, um die Kontrolle aus einem verschachtelten Bereich heraus zu übertragen.Hinweisende
Das Ziel einer goto case-Anweisung ist die Anweisungsliste in der unmittelbar einschließenden switch-Anweisung (§13.8.3), die eine case-Kennzeichnung mit einem konstanten Muster des angegebenen konstanten Werts und keinen Guard enthält. Wenn die goto case Anweisung nicht von einer switch Anweisung eingeschlossen ist, wenn die nächstgelegene umgebende switch Anweisung keine solche case enthält oder die constant_expression nicht implizit (§10.2) in den Regeltyp der nächstgelegenen umgebenden switch Anweisung konvertierbar ist, tritt ein Kompilierfehler auf.
Das Ziel einer goto default Anweisung ist die Anweisungsliste in der unmittelbar eingeschlossenen switch Anweisung (§13.8.3), die eine default Bezeichnung enthält. Wenn die goto default Anweisung nicht von einer switch Anweisung eingeschlossen ist oder die nächste eingeschlossene switch Anweisung keine default Bezeichnung enthält, tritt ein Kompilierungszeitfehler auf.
Eine goto Anweisung kann einen finally Block nicht beenden (§13.11). Wenn eine goto Anweisung innerhalb eines finally Blocks auftritt, muss sich das Ziel der goto Anweisung innerhalb desselben finally Blocks befinden oder andernfalls tritt ein Kompilierungsfehler auf.
Eine goto Anweisung wird wie folgt ausgeführt:
- Wenn die
goto-Anweisung einen oder mehreretry-Blöcke mit zugeordnetenfinally-Blöcken verlässt, wird die Kontrolle zunächst an denfinally-Block der innerstentry-Anweisung übertragen. Wenn und falls die Kontrolle den Endpunkt einesfinally-Blocks erreicht, wird die Kontrolle an denfinally-Block der nächsten einschließendentry-Anweisung übertragen. Dieser Vorgang wird wiederholt, bis diefinallyBlöcke aller dazwischen liegendentryAnweisungen ausgeführt wurden. - Die Steuerung wird an das Ziel der
gotoAnweisung übertragen.
Da eine goto Aussage die Kontrolle an anderer Stelle bedingungslos überträgt, ist der Endpunkt einer goto Aussage nie erreichbar.
13.10.5 Die Return-Anweisung
Die return Anweisung gibt die Steuerung an den aktuellen Aufrufer des Funktionsmitglieds zurück, in dem die Rückgabeanweisung vorkommt, indem sie optional einen Wert oder eine variable_reference (§9.5) zurückgibt.
return_statement
: 'return' ';'
| 'return' expression ';'
| 'return' 'ref' variable_reference ';'
;
Ein return_statement ohne Ausdruck wird return-no-value genannt; eines, das refAusdruck enthält, wird return-by-ref genannt; und eines, das nur Ausdruck enthält, wird return-by-value genannt.
Es ist ein Kompilierfehler, einen return-no-value aus einer Methode zu verwenden, die als return-by-value oder return-by-ref deklariert ist (§15.6.1).
Es ist ein Kompilierfehler, einen return-by-ref von einer Methode zu verwenden, die als return-no-value oder return-by-value deklariert ist.
Es ist ein Kompilierfehler, einen return-by-value von einer Methode zu verwenden, die als returns-no-value oder return-by-ref deklariert ist.
Es handelt sich um einen Kompilierfehler bei der Verwendung von return-by-ref, wenn der Ausdruck keine Variablenreferenz ist oder es sich um einen Verweis auf eine Variable handelt, deren ref-safe-context nicht der Aufruferkontext ist (§9.7.2).
Es ist ein Kompilierungsfehler, eine Rückgabe per Referenz aus einer Methode zu verwenden, die mit dem method_modifierasync deklariert ist.
Von einem Funktionsmitglied wird gesagt, dass es einen Wert berechnet, wenn es eine Methode mit einer Return-by-Value-Methode (§15.6.11), ein Return-by-Value-Get-Accessor einer Eigenschaft oder eines Indexers oder ein benutzerdefinierter Operator ist. Funktionsmitglieder, die returns-no-value sind, berechnen keinen Wert und sind Methoden mit dem effektiven Rückgabetyp void, Set-Accessoren von Eigenschaften und Indexern, Add- und Remove-Accessoren von Ereignissen, Instanz-Konstruktoren, statische Konstruktoren und Finalizer. Funktionsmitglieder, die return-by-ref sind, berechnen keinen Wert.
Für eine Rückgabe durch Wert muss eine implizite Konvertierung (§10.2) vom Typ des Ausdrucks in den effektiven Rückgabetyp (§15.6.11) des enthaltenden Funktionselements bestehen. Bei einem return-by-ref muss eine Identitätskonversion (§10.2.2) zwischen dem Typ von Ausdruck und dem effektiven Rückgabetyp des enthaltenden Funktionsmitglieds bestehen.
return Anweisungen können auch im Textkörper anonymer Funktionsausdrücke (§12.21) verwendet werden und daran teilnehmen, zu bestimmen, welche Konvertierungen für diese Funktionen vorhanden sind (§10.7.1).
Es handelt sich um einen Kompilierungszeitfehler für eine return Anweisung, die in einem finally Block (§13.11) angezeigt wird.
Eine return Anweisung wird wie folgt ausgeführt:
- Bei einem Return-by-Value wird Ausdruck ausgewertet und sein Wert durch eine implizite Konversion in den effektiven Rückgabetyp der enthaltenen Funktion umgewandelt. Das Ergebnis der Konvertierung wird zum Ergebniswert, der von der Funktion erzeugt wird. Bei einem return-by-ref wird der Ausdruck ausgewertet und das Ergebnis wird als Variable klassifiziert. Wenn der Return-by-Ref der einschließenden Methode
readonlyenthält, ist die resultierende Variable schreibgeschützt. - Wenn die
returnAnweisung von einem oder mehrerentrycatchBlöcken mit zugeordnetenfinallyBlöcken eingeschlossen wird, wird die Steuerung zunächst in denfinallyBlock der innerstentryAnweisung übertragen. Wenn und falls die Kontrolle den Endpunkt einesfinally-Blocks erreicht, wird die Kontrolle an denfinally-Block der nächsten einschließendentry-Anweisung übertragen. Dieser Vorgang wird so lange wiederholt, bis diefinallyBlockierungen aller einschließendentryAnweisungen ausgeführt wurden. - Wenn die enthaltende Funktion keine asynchrone Funktion ist, wird das Steuerelement an den Aufrufer der enthaltenden Funktion zusammen mit dem Ergebniswert zurückgegeben, falls vorhanden.
- Wenn die enthaltende Funktion eine asynchrone Funktion ist, wird das Steuerelement an den aktuellen Aufrufer zurückgegeben, und der Ergebniswert (falls vorhanden) wird in der Rückgabeaufgabe aufgezeichnet, wie in (§15.14.3) beschrieben.
Da eine return Aussage die Kontrolle an anderer Stelle bedingungslos überträgt, ist der Endpunkt einer return Aussage nie erreichbar.
13.10.6 Die Throw-Anweisung
Die Anweisung throw löst eine Ausnahme aus.
throw_statement
: 'throw' expression? ';'
;
Eine throw-Anweisung mit einem Ausdruck löst eine Ausnahme aus, die durch die Auswertung des Ausdrucks erzeugt wird. Der Ausdruck soll implizit in System.Exception konvertierbar sein, und das Ergebnis der Auswertung des Ausdrucks wird in System.Exception umgewandelt, bevor es ausgelöst wird. Wenn das Ergebnis der Konvertierung null ist, wird stattdessen ein System.NullReferenceException ausgelöst.
Eine throw-Anweisung ohne Ausdruck kann nur in einem catch-Block verwendet werden. In diesem Fall löst diese Anweisung die Ausnahme, die aktuell von diesem catch-Block behandelt wird, erneut aus.
Da eine throw Aussage die Kontrolle an anderer Stelle bedingungslos überträgt, ist der Endpunkt einer throw Aussage nie erreichbar.
Wenn eine Ausnahme ausgelöst wird, wird die Kontrolle an die erste catch-Klausel in einer einschließenden try-Anweisung übertragen, die die Ausnahme behandeln kann. Der Prozess, der vom Auftreten einer Ausnahme bis zur Übergabe der Kontrolle an einen geeigneten Ausnahme-Handler abläuft, wird als Ausbreitung der Ausnahme bezeichnet. Die Verteilung einer Ausnahme besteht aus wiederholter Auswertung der folgenden Schritte, bis eine catch Klausel gefunden wird, die der Ausnahme entspricht. In dieser Beschreibung ist der Wurfpunkt zunächst der Ort, an dem die Ausnahme ausgelöst wird. Dieses Verhalten wird in (§22.4) angegeben.
Im aktuellen Funktionsmitglied wird jede
try-Anweisung untersucht, die den Wurfpunkt einschließt. Für jede AnweisungS, beginnend mit der innerstentryAnweisung und endet mit der äußerstentryAnweisung, werden die folgenden Schritte ausgewertet:- Wenn der
try-Block vonSden Übergabepunkt einschließt und wennSeine oder mehrerecatch-Klauseln enthält, werden diecatch-Klauseln in der Reihenfolge ihres Erscheinens untersucht, um einen geeigneten Handler für die Ausnahme zu finden. Die erstecatch-Klausel, die einen AusnahmetypT(oder einen Typparameter, der zur Laufzeit einen AusnahmetypTbezeichnet) angibt, sodass sich der LaufzeittypEvonTableitet, wird als Übereinstimmung betrachtet. Wenn die Klausel einen Ausnahmefilter enthält, wird das Ausnahmeobjekt der Ausnahmevariablen zugewiesen, und der Ausnahmefilter wird ausgewertet. Wenn einecatch-Klausel einen Ausnahmefilter enthält, gilt diecatch-Klausel als übereinstimmend, wenn der Ausnahmefilter zutrueausgewertet wird. Eine allgemeinecatch(§13.11) Klausel wird für jeden Ausnahmetyp als übereinstimmend angesehen. Wenn eine übereinstimmendecatch-Klausel gefunden wird, wird die Weitergabe der Ausnahme abgeschlossen, indem die Kontrolle auf den Block diesercatch-Klausel übertragen wird. - Andernfalls, wenn der
tryBlock oder eincatchBlock vonSden Wurfpunkt umschließt undSüber einenfinallyBlock verfügt, wird die Kontrolle an denfinallyBlock übergeben. Wenn derfinally-Block eine weitere Ausnahme auslöst, wird die Verarbeitung der aktuellen Ausnahme beendet. Andernfalls wird die Verarbeitung der aktuellen Ausnahme fortgesetzt, wenn das Steuerelement den Endpunkt desfinallyBlocks erreicht.
- Wenn der
Wenn sich ein Ausnahmehandler nicht im aktuellen Funktionsaufruf befindet, wird der Funktionsaufruf beendet, und einer der folgenden Aktionen tritt auf:
Wenn die aktuelle Funktion nicht asynchron ist, werden die obigen Schritte für den Aufrufer der Funktion mit einem Wurfpunkt wiederholt, der der Anweisung entspricht, aus der das Funktionselement aufgerufen wurde.
Wenn die aktuelle Funktion asynchron und task-returning ist, wird die Ausnahme in der Rückgabeaufgabe aufgezeichnet, die in einen fehlerhaften oder abgebrochenen Zustand versetzt wird, wie in §15.14.3 beschrieben.
Wenn die aktuelle Funktion asynchron ist und
void-zurückkehrt, wird der Synchronisationskontext des aktuellen Threads wie in §15.14.4 beschrieben benachrichtigt.
Wenn die Ausnahmeverarbeitung alle Funktionsmemembeaufrufe im aktuellen Thread beendet, was angibt, dass der Thread keinen Handler für die Ausnahme hat, wird der Thread selbst beendet. Die Auswirkungen einer solchen Beendigung sind implementierungsabhängig.
13.11 Die try-Anweisung
Die try Anweisung stellt einen Mechanismus zum Abfangen von Ausnahmen bereit, die während der Ausführung eines Blocks auftreten. Darüber hinaus bietet die try Anweisung die Möglichkeit, einen Codeblock anzugeben, der immer ausgeführt wird, wenn das Steuerelement die try Anweisung verlässt.
try_statement
: 'try' block catch_clauses
| 'try' block catch_clauses? finally_clause
;
catch_clauses
: specific_catch_clause+
| specific_catch_clause* general_catch_clause
;
specific_catch_clause
: 'catch' exception_specifier exception_filter? block
| 'catch' exception_filter block
;
exception_specifier
: '(' type identifier? ')'
;
exception_filter
: 'when' '(' boolean_expression ')'
;
general_catch_clause
: 'catch' block
;
finally_clause
: 'finally' block
;
Ein try_statement besteht aus dem Schlüsselwort try gefolgt von einem Block, dann null oder mehr catch_clauses und dann einem optionalen finally_clause. Es muss mindestens eine catch_clause oder eine finally_clause vorhanden sein.
In einem exception_specifier muss der Typ oder seine effektive Basisklasse, wenn es sich um einen type_parameter handelt, System.Exception oder ein Typ sein, der von ihm abgeleitet wird.
Wenn eine catch Klausel sowohl eine class_type als auch einen Bezeichner angibt, wird eine Ausnahmevariable des angegebenen Namens und Typs deklariert. Die Ausnahmevariable wird in den Deklarationsbereich des specific_catch_clause (§7.3) eingeführt. Während der Ausführung des exception_filter und catch Blocks repräsentiert die Ausnahmevariable die Ausnahme, die aktuell behandelt wird. Für die Zwecke der endgültigen Zuordnungsprüfung gilt die Ausnahmevariable in ihrem gesamten Umfang als definitiv zugewiesen.
Sofern keine catch Klausel einen Ausnahmevariablennamen enthält, ist es unmöglich, auf das Ausnahmeobjekt im Filter und catch Block zuzugreifen.
Eine catch Klausel, die weder einen Ausnahmetyp noch einen Ausnahmevariablennamen angibt, wird als allgemeine catch Klausel bezeichnet. Eine try Erklärung kann nur eine allgemeine catch Klausel haben, und wenn eine klausel vorhanden ist, ist sie die letzte catch Klausel.
Hinweis: Einige Programmiersprachen unterstützen möglicherweise Ausnahmen, die nicht als von
System.Exceptionabgeleitetes Objekt dargestellt werden können, obwohl solche Ausnahmen niemals durch C#-Code generiert werden könnten. Eine allgemeinecatchKlausel kann verwendet werden, um solche Ausnahmen abzufangen. Daher unterscheidet sich eine allgemeinecatchKlausel semantisch von einem, der den TypSystem.Exceptionangibt, in dem der frühere auch Ausnahmen von anderen Sprachen abfangen kann. Hinweisende
Um einen Handler für eine Ausnahme zu finden, werden die catch-Klauseln in lexikalischer Reihenfolge untersucht. Wenn eine catch Klausel einen Typ, aber keinen Ausnahmefilter angibt, handelt es sich um einen Kompilierungszeitfehler für eine spätere catch Klausel derselben try Anweisung, um einen Typ anzugeben, der demselben Typ entspricht oder von diesem Typ abgeleitet wird.
Hinweis: Ohne diese Einschränkung wäre es möglich, nicht erreichbare
catchKlauseln zu schreiben. Hinweisende
Innerhalb eines catch-Blocks kann eine throw-Anweisung (§13.10.6) ohne Ausdruck verwendet werden, um die Ausnahme, die durch den catch-Block abgefangen wurde, erneut auszulösen. Zuordnungen zu einer Ausnahmevariablen ändern nicht die ausnahme, die erneut ausgelöst wird.
Beispiel: Im folgenden Code
class Test { static void F() { try { G(); } catch (Exception e) { Console.WriteLine("Exception in F: " + e.Message); e = new Exception("F"); throw; // re-throw } } static void G() => throw new Exception("G"); static void Main() { try { F(); } catch (Exception e) { Console.WriteLine("Exception in Main: " + e.Message); } } }Die Methode
Ffängt eine Ausnahme ab, schreibt einige Diagnoseinformationen in die Konsole, ändert die Ausnahmevariable und löst die Ausnahme erneut aus. Die neu ausgelöste Ausnahme ist die ursprüngliche Ausnahme, sodass die Ausgabe, die erzeugt wird, wie folgt lautet:Exception in F: G Exception in Main: GWenn der erste
catchBlockeausgelöst hätte, anstatt die aktuelle Ausnahme erneut auszulösen, würde die erzeugte Ausgabe wie folgt aussehen:Exception in F: G Exception in Main: FEndbeispiel
Es handelt sich um einen Kompilierungszeitfehler, wenn eine break, continue oder goto-Anweisung die Kontrolle aus einem finally-Block überträgt. Wenn eine break Anweisung, continue Anweisung oder goto Anweisung in einem finally Block auftritt, muss sich das Ziel der Anweisung innerhalb desselben finally Blocks befinden, oder andernfalls tritt ein Fehler zur Kompilierungszeit auf.
Es handelt sich um einen Kompilierungszeitfehler für eine return Anweisung, die in einem finally Block auftritt.
Wenn die Ausführung eine try Anweisung erreicht, wird das Steuerelement in den try Block übertragen. Wenn das Steuerelement den Endpunkt des try Blocks erreicht, ohne dass eine Ausnahme weitergegeben wird, wird das Steuerelement an den finally Block übertragen, falls vorhanden. Wenn kein finally Block vorhanden ist, wird das Steuerelement an den Endpunkt der try Anweisung übertragen.
Wenn eine Ausnahme weitergegeben wurde, werden die catch Klauseln (falls vorhanden) in lexikalischer Reihenfolge untersucht, in der die erste Übereinstimmung für die Ausnahme gesucht wird. Die Suche nach einer Abgleichsklausel catch wird mit allen eingeschlossenen Blöcken fortgesetzt, wie in §13.10.6 beschrieben. Eine catch Klausel stimmt überein, wenn der Ausnahmetyp mit irgendeinem Ausnahmespezifizierer übereinstimmt und irgendein Ausnahmefilter wahr ist. Eine catch Klausel ohne einen Ausnahmespezifizierer entspricht jedem Ausnahmetyp. Der Ausnahmetyp entspricht dem exception_specifier , wenn der exception_specifier den Ausnahmetyp oder einen Basistyp des Ausnahmetyps angibt. Wenn die Klausel einen Ausnahmefilter enthält, wird das Ausnahmeobjekt der Ausnahmevariablen zugewiesen, und der Ausnahmefilter wird ausgewertet. Wenn die Auswertung der boolean_expression für eine exception_filter eine Ausnahme auslöst, wird diese Ausnahme abgefangen, und der Ausnahmefilter wird ausgewertet.false
Wenn eine Ausnahme weitergegeben wurde und eine Abgleichsklausel catch gefunden wird, wird das Steuerelement in den ersten übereinstimmenden catch Block übertragen. Wenn das Steuerelement den Endpunkt des catch Blocks erreicht, ohne dass eine Ausnahme weitergegeben wird, wird das Steuerelement an den finally Block übertragen, falls vorhanden. Wenn kein finally Block vorhanden ist, wird das Steuerelement an den Endpunkt der try Anweisung übertragen. Wenn eine Ausnahme aus dem catch Block weitergegeben wurde, wird die Steuerung an den finally Block übertragen, falls vorhanden. Die Ausnahme wird an die nächste einschließende try-Anweisung weitergegeben.
Wenn eine Ausnahme weitergegeben wurde und keine Übereinstimmende catch Klausel gefunden wird, wird die Steuerung an den finally Block übertragen, sofern vorhanden. Die Ausnahme wird an die nächste einschließende try-Anweisung weitergegeben.
Die Anweisungen eines finally-Blocks werden immer ausgeführt, wenn die Steuerung eine try-Anweisung verlässt. Dies gilt unabhängig davon, ob die Kontrollübergabe als Ergebnis einer normalen Ausführung erfolgt, durch die Ausführung eines break-, continue-, goto- oder return-Statements zustande kommt oder durch das Weitergeben einer Ausnahme aus einem try-Statement verursacht wird. Wenn das Steuerelement den Endpunkt des finally Blocks erreicht, ohne dass eine Ausnahme weitergegeben wird, wird das Steuerelement an den Endpunkt der try Anweisung übertragen.
Wenn während der Ausführung eines finally Blocks eine Ausnahme ausgelöst wird und nicht innerhalb desselben finally Blocks abgefangen wird, wird die Ausnahme an die nächste eingeschlossene try Anweisung weitergegeben. Wenn eine andere Ausnahme gerade im Begriff war, weitergeleitet zu werden, geht diese Ausnahme verloren. Der Prozess der Weitergabe einer Ausnahme wird weiter in der Beschreibung der throw Erklärung (§13.10.6) erläutert.
Beispiel: Im folgenden Code
public class Test { static void Main() { try { Method(); } catch (Exception ex) when (ExceptionFilter(ex)) { Console.WriteLine("Catch"); } bool ExceptionFilter(Exception ex) { Console.WriteLine("Filter"); return true; } } static void Method() { try { throw new ArgumentException(); } finally { Console.WriteLine("Finally"); } } }die Methode
Methodlöst eine Ausnahme aus. Die erste Aktion besteht darin, die eingeschlossenencatchKlauseln zu untersuchen und alle Ausnahmefilter auszuführen. ** Anschließend wird diefinally-Klausel inMethodausgeführt, bevor die Steuerung an die umschließende passendecatch-Klausel übertragen wird. Die resultierende Ausgabe ist:Filter Finally CatchEndbeispiel
Der try Block einer try Anweisung ist erreichbar, wenn die try Anweisung erreichbar ist.
Ein catch Block einer try Anweisung ist erreichbar, wenn die try Anweisung erreichbar ist.
Der finally Block einer try Anweisung ist erreichbar, wenn die try Anweisung erreichbar ist.
Der Endpunkt einer try Anweisung ist erreichbar, wenn beides zutrifft:
- Der Endpunkt des
tryBlocks ist erreichbar oder der Endpunkt mindestens einescatchBlocks ist erreichbar. - Wenn ein
finallyBlock vorhanden ist, ist der Endpunkt desfinallyBlocks erreichbar.
13.12 Die Anweisungen checked und unchecked
Die Anweisungen checked und unchecked werden verwendet, um den Überlaufprüfungskontext für arithmetische Operationen und Konversionen vom Typ Integraltypen zu steuern.
checked_statement
: 'checked' block
;
unchecked_statement
: 'unchecked' block
;
Die checked Anweisung bewirkt, dass alle Ausdrücke im Block in einem überprüften Kontext ausgewertet werden, und die unchecked Anweisung bewirkt, dass alle Ausdrücke im Block in einem deaktivierten Kontext ausgewertet werden.
Die checked und unchecked Anweisungen sind exakt gleichbedeutend mit den checked und unchecked Operatoren (§12.8.20), außer dass sie auf Blöcken statt auf Ausdrücken operieren.
13.13 Die Lock-Anweisung
Die lock Anweisung ruft die Sperre für den gegenseitigen Ausschluss für ein bestimmtes Objekt ab, führt eine Anweisung aus und gibt dann die Sperre frei.
lock_statement
: 'lock' '(' expression ')' embedded_statement
;
Der Ausdruck einer lockAnweisung muss einen Wert eines Typs bezeichnen, von dem bekannt ist, dass er eine Referenz ist. Für den Ausdruck einer Anweisung wird niemals eine implizite Konversion in eine Box durchgeführt (lock). Daher ist es ein Kompilierfehler, wenn der Ausdruck einen Wert eines Werttyps bezeichnet.
Eine lock-Anweisung der Form
lock (x) …
wobei x ein Ausdruck eines reference_type ist, ist genau äquivalent zu:
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(x, ref __lockWasTaken);
...
}
finally
{
if (__lockWasTaken)
{
System.Threading.Monitor.Exit(x);
}
}
außer dass x nur einmal überprüft wird.
Während eine Sperre mit gegenseitigem Ausschluss gehalten wird, kann Code, der im selben Ausführungsthread ausgeführt wird, die Sperre auch abrufen und freigeben. Code, der in anderen Threads ausgeführt wird, wird jedoch daran gehindert, die Sperre abzurufen, bis die Sperre freigegeben wird.
13.14 Die using-Anweisung
13.14.1 Allgemein
Die using Anweisung ruft eine oder mehrere Ressourcen ab, führt eine Anweisung aus und entsorgt dann die Ressource.
using_statement
: 'await'? 'using' '(' resource_acquisition ')' embedded_statement
;
resource_acquisition
: non_ref_local_variable_declaration
| expression
;
non_ref_local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
;
Ein Ressourcentyp ist entweder eine Klasse oder eine Nicht-Verweis-Struktur, die eine oder beide der System.IDisposableSystem.IAsyncDisposable Schnittstellen implementiert, die eine einzelne parameterlose Methode namens Dispose und/oder DisposeAsynceine Referenzstruktur enthält, die eine Methode Dispose enthält, die dieselbe Signatur wie die von System.IDisposabledieser deklariert hat. Code, der eine Ressource verwendet, kann aufrufen Dispose oder DisposeAsync angeben, dass die Ressource nicht mehr benötigt wird.
Wenn die Form von resource_acquisitionlocal_variable_declaration ist, muss der Typ des local_variable_declaration entweder dynamic oder ein Ressourcentyp sein. Wenn die Form von resource_acquisitionAusdruck ist, hat dieser Ausdruck einen Ressourcentyp. Falls await vorhanden, muss der Ressourcentyp implementiert System.IAsyncDisposablewerden. Ein ref struct Typ kann nicht der Ressourcentyp für eine using Anweisung mit dem await Modifizierer sein.
Lokale Variablen, die in resource_acquisition deklariert sind, sind schreibgeschützt und müssen einen Initialisierer enthalten. Ein Kompilierungszeitfehler tritt auf, wenn die eingebettete Anweisung versucht, diese lokalen Variablen (über zuordnung oder die ++ Operatoren -- ) zu ändern, die Adresse davon zu übernehmen oder sie als Referenz- oder Ausgabeparameter zu übergeben.
Ein using Statement wird in drei Abschnitte übersetzt: Erwerb, Nutzung und Entsorgung. Die Verwendung der Ressource wird implizit in eine try Anweisung eingeschlossen, die eine finally Klausel enthält. Diese finally Klausel entsorgt die Ressource. Wenn der Kaufausdruck ausgewertet wird null, wird kein Aufruf von Dispose (oder DisposeAsync) ausgeführt, und es wird keine Ausnahme ausgelöst. Wenn die Ressource vom Typ dynamic ist, wird sie während des Erwerbs dynamisch über eine implizite dynamische Konvertierung (§10.2.10) in IDisposable (oder IAsyncDisposable) konvertiert, um sicherzustellen, dass die Konvertierung vor der Verwendung und Entsorgung erfolgreich ist.
Eine using-Anweisung der Form
using (ResourceType resource = «expression» ) «statement»
entspricht einer von drei möglichen Formulierungen. Bei Klassen- und Nicht-Referenzressourcen ist die Formulierung semantisch gleichbedeutend mit dem Werttypeinschränkung (ResourceType), wenn es sich um einen nicht nullablen Werttyp oder einen Typparameter handelt.
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
((IDisposable)resource).Dispose();
}
}
mit der Ausnahme, dass die Umwandlung von resource in System.IDisposable nicht zu einem Boxing führen darf.
ResourceType Andernfalls ist dynamicdie Formulierung
{
ResourceType resource = «expression»;
IDisposable d = resource;
try
{
«statement»;
}
finally
{
if (d != null)
{
d.Dispose();
}
}
}
Andernfalls ist die Formulierung
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IDisposable d = (IDisposable)resource;
if (d != null)
{
d.Dispose();
}
}
}
Bei Refstrukturressourcen ist die einzige semantisch gleichwertige Formulierung
{
«ResourceType» resource = «expression»;
try
{
«statement»;
}
finally
{
resource.Dispose();
}
}
In jeder Formulierung ist die resource Variable in der eingebetteten Anweisung schreibgeschützt, und die d Variable ist in der eingebetteten Anweisung nicht zugänglich und für die eingebettete Anweisung unsichtbar.
Eine using-Anweisung in der Form:
using («expression») «statement»
hat die gleichen möglichen Formulierungen.
Wenn ein resource_acquisition die Form eines local_variable_declaration verwendet, ist es möglich, mehrere Ressourcen eines bestimmten Typs zu erwerben. Eine using-Anweisung der Form
using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) «statement»
entspricht genau einer Sequenz von verschachtelten using-Anweisungen:
using (ResourceType r1 = e1)
using (ResourceType r2 = e2)
...
using (ResourceType rN = eN)
«statement»
Beispiel: Im folgenden Beispiel wird eine Datei mit dem Namen log.txt erstellt und zwei Textzeilen in die Datei geschrieben. Anschließend wird die gleiche Datei zum Lesen geöffnet und die darin enthaltenen Textzeilen in die Konsole kopiert.
class Test { static void Main() { using (TextWriter w = File.CreateText("log.txt")) { w.WriteLine("This is line one"); w.WriteLine("This is line two"); } using (TextReader r = File.OpenText("log.txt")) { string s; while ((s = r.ReadLine()) != null) { Console.WriteLine(s); } } } }Da die Klassen
TextWriterundTextReaderdie SchnittstelleIDisposableimplementieren, kann das BeispielusingAnweisungen verwenden, um sicherzustellen, dass die zugrunde liegende Datei nach den Schreib- oder Leseoperationen ordnungsgemäß abgeschlossen wird.Endbeispiel
Wenn ResourceType es sich um einen Verweistyp handelt, der implementiert IAsyncDisposablewird. Andere Formulierungen für await using ähnliche Ersetzungen von der synchronen Dispose Methode zur asynchronen DisposeAsync Methode. Eine await using Anweisung der Form
await using (ResourceType resource = «expression» ) «statement»
ist semantisch gleichbedeutend mit den unten gezeigten Formulierungen anstelle IAsyncDisposable von IDisposable, DisposeAsync anstelle von Dispose, und die Task zurückgegebene von DisposeAsync wird await:
await using (ResourceType resource = «expression» ) «statement»
ist semantisch gleichbedeutend mit
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IAsyncDisposable d = (IAsyncDisposable)resource;
if (d != null)
{
await d.DisposeAsync();
}
}
}
Hinweis: Alle Sprunganweisungen (§13.10) im embedded_statement müssen der erweiterten Form der
usingAnweisung entsprechen. Hinweisende
13.14.2 Verwendung der Deklaration
Eine syntaktische Variante der using-Anweisung ist eine using-Deklaration.
using_declaration
: 'await'? 'using' non_ref_local_variable_declaration ';' statement_list?
;
Eine using-Deklaration weist die gleiche Semantik auf wie die entsprechende Ressourcenerwerbsform der using-Anweisung (§13.14.1) wie folgt:
using «local_variable_type» «local_variable_declarators»
// statements
ist semantisch gleichbedeutend mit
using («local_variable_type» «local_variable_declarators»)
{
// statements
}
und
await using «local_variable_type» «local_variable_declarators»
// statements
ist semantisch gleichbedeutend mit
await using («local_variable_type» «local_variable_declarators»)
{
// statements
}
Die Lebensdauer der in einem non_ref_local_variable_declaration deklarierten Variablen erstreckt sich bis zum Ende des Bereichs, in dem sie deklariert werden. Diese Variablen werden dann in der umgekehrten Reihenfolge verworfen, in der sie deklariert werden.
static void M()
{
using FileStream f1 = new FileStream(...);
using FileStream f2 = new FileStream(...), f3 = new FileStream(...);
...
// Dispose f3
// Dispose f2
// Dispose f1
}
Eine Verwendungsdeklaration wird nicht direkt innerhalb eines switch_label angezeigt, sondern kann sich stattdessen innerhalb eines Blocks innerhalb eines switch_label befinden.
13.15 Die Renditeerklärung
Die yield Anweisung wird in einem Iteratorblock (§13.3) verwendet, um dem Enumerationsobjekt (§15.15.5) oder dem aufzählbaren Objekt (§15.15.6) eines Iterators einen Wert zu liefern oder das Ende der Iteration zu signalisieren.
yield_statement
: 'yield' 'return' expression ';'
| 'yield' 'break' ';'
;
yield ist ein kontextbezogenes Schlüsselwort (§6.4.4) und hat nur dann besondere Bedeutung, wenn es unmittelbar vor einem return oder break Schlüsselwort verwendet wird.
Es gibt mehrere Einschränkungen, wo eine yield-Anweisung erscheinen kann, wie im Folgenden beschrieben.
- Es ist ein Kompilierzeitfehler, wenn eine
yieldAnweisung (in einer der beiden Formen) außerhalb eines method_body, operator_body oder accessor_body vorkommt. - Es ist ein Kompilierfehler, wenn eine
yield-Anweisung (in irgendeiner Form) innerhalb einer anonymen Funktion erscheint. - Es ist ein Kompilierfehler, wenn eine
yield-Anweisung (in beliebiger Form) in derfinally-Klausel einertry-Anweisung erscheint. - Es ist ein Kompilierzeitfehler, wenn eine
yield returnAnweisung an einer beliebigen Stelle in einertryAnweisung erscheint, die irgendwelche catch_clauses enthält.
Beispiel: Das folgende Beispiel zeigt einige gültige und ungültige Verwendungen von
yieldAnweisungen.delegate IEnumerable<int> D(); IEnumerator<int> GetEnumerator() { try { yield return 1; // Ok yield break; // Ok } finally { yield return 2; // Error, yield in finally yield break; // Error, yield in finally } try { yield return 3; // Error, yield return in try/catch yield break; // Ok } catch { yield return 4; // Error, yield return in try/catch yield break; // Ok } D d = delegate { yield return 5; // Error, yield in an anonymous function }; } int MyMethod() { yield return 1; // Error, wrong return type for an iterator block }Endbeispiel
Eine implizite Konvertierung (§10.2) muss vom Typ des Ausdrucks in der yield return Anweisung in den Ertragtyp (§15.15.4) des Iterators bestehen.
Eine yield return Anweisung wird wie folgt ausgeführt:
- Der in der Anweisung angegebene Ausdruck wird ausgewertet, implizit in den Ertragtyp konvertiert und der
CurrentEigenschaft des Enumeratorobjekts zugewiesen. - Die Ausführung des Iterator-Blocks wird unterbrochen. Wenn sich die
yield returnAnweisung in einem odertrymehreren Blöcken befindet, werden die zugehörigenfinallyBlöcke zurzeit nicht ausgeführt. - Die Methode
MoveNextdes Aufzählungsobjekts gibttruean den Aufrufer zurück und zeigt an, dass das Aufzählungsobjekt erfolgreich zum nächsten Element weitergeschaltet wurde.
Der nächste Aufruf der Enumerator-Objektmethode MoveNext setzt die Ausführung des Iteratorblocks fort, von wo aus es zuletzt angehalten wurde.
Eine yield break Anweisung wird wie folgt ausgeführt:
- Wenn die
yield breakAnweisung von einem odertrymehreren Blöcken mit zugeordnetenfinallyBlöcken eingeschlossen wird, wird das Steuerelement zunächst in denfinallyBlock der innerstentryAnweisung übertragen. Wenn und falls die Kontrolle den Endpunkt einesfinally-Blocks erreicht, wird die Kontrolle an denfinally-Block der nächsten einschließendentry-Anweisung übertragen. Dieser Vorgang wird so lange wiederholt, bis diefinallyBlockierungen aller einschließendentryAnweisungen ausgeführt wurden. - Die Kontrolle wird an den Aufrufer des Iteratorblocks zurückgegeben. Dies ist entweder die
MoveNextMethode oderDisposeMethode des Enumeratorobjekts.
Da eine yield break Aussage die Kontrolle an anderer Stelle bedingungslos überträgt, ist der Endpunkt einer yield break Aussage nie erreichbar.
ECMA C# draft specification