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 (§23.2) und fixed_statement (§23.7) sind nur im unsicheren Code (§23) 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
if
Anweisung für ihren Branch ein embedded_statement und nicht einif
benötigt. Wenn dieser Code zulässig wäre, würde die Variablei
deklariert, aber sie konnte nie verwendet werden. Beachten Sie jedoch, dass das Beispiel gültig ist, indem die Deklaration in einem Block platzierti
wird.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.23), 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
if
Anweisung ist ein konstanter Ausdruck, da beide Operanden des==
Operators Konstanten sind. Wenn der konstante Ausdruck während der Kompilierung ausgewertet wird, wird der Wertfalse
erzeugt, und derConsole.WriteLine
-Aufruf wird als nicht erreichbar betrachtet. Allerdings, wenni
in eine lokale Variable geändert wirdvoid F() { int i = 1; if (i == 2) Console.WriteLine("reachable"); }
der
Console.WriteLine
Aufruf 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.WriteLine
wird wie folgt bestimmt:
- Die erste
Console.WriteLine
Ausdrucksanweisung ist erreichbar, da der Block derF
Methode erreichbar ist (§13.3).- Der Endpunkt der ersten
Console.WriteLine
Ausdrucksanweisung ist erreichbar, da diese Anweisung erreichbar ist (§13.7 und §13.3).- Die
if
Anweisung ist erreichbar, da der Endpunkt der erstenConsole.WriteLine
Ausdrucksanweisung erreichbar ist (§13.7 und §13.3).- Die zweite
Console.WriteLine
Ausdrucksanweisung ist erreichbar, da der boolesche Ausdruck derif
Anweisung nicht den Konstantenwertfalse
aufweist.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 einebreak
Anweisung 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
return
Anweisung 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
return
Anweisung, die in einem Iteratorblock angezeigt wird (aberyield return
Anweisungen sind zulässig). - Es handelt sich um einen Kompilierungszeitfehler für einen Iteratorblock, der einen unsicheren Kontext (§23.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
while
Anweisung 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
goto
Anweisungen 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
var
im Bereich gibt und die Eingabe mit implicitly_typed_local_variable_declaration übereinstimmt, dann wird dieser gewählt; - Andernfalls, wenn ein Typ mit dem Namen
var
im 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).
Anmerkung: Für eine local_variable_declaration, die nicht als for_initializer (§13.9.4) oder resource_acquisition (§13.14) auftritt, ist diese Reihenfolge von links nach rechts äquivalent zu jedem Deklarator innerhalb einer 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 ref
variable_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 itself
Endbeispiel
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 explicity_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.21.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 vom Typ type sein und die gleichen Anforderungen erfüllen wie für eine ref Zuweisung (§12.21.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.23), 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
(§23.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 (§23.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 und
this
spiegeln die Regeln für anonyme Funktionen ingoto
wider. 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
L
erreichbar, obwohl der AnfangspunktL
nicht erreichbar ist. Da der AnfangspunktL
nicht erreichbar ist, ist die Anweisung nach dem EndpunktL
nicht 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
this
oderbase
noch 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 + y
undx == 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.24) 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 derif
Anweisung übertragen. - Wenn der boolesche Ausdruck den Wert
false
ergibt und einelse
Element 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 derif
Anweisung übertragen. - Wenn der boolesche Ausdruck
false
ergibt 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 false
aufweist.
Die zweite eingebettete Anweisung einer if
Anweisung, falls vorhanden, ist erreichbar, wenn die if
Anweisung erreichbar ist und der boolesche Ausdruck nicht den Konstantenwert true
aufweist.
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 true
aufweist.
13.8.3 Die Switch-Anweisung
Die switch
Anweisung wählt eine Anweisungsliste zur Ausführung aus, die mit einem Switch-Label verknüpft ist, das dem Wert des Switch-Ausdrucks entspricht.
switch_statement
: 'switch' '(' expression ')' switch_block
;
switch_block
: '{' switch_section* '}'
;
switch_section
: switch_label+ statement_list
;
switch_label
: 'case' pattern case_guard? ':'
| 'default' ':'
;
case_guard
: 'when' expression
;
Ein switch_statement besteht aus dem Schlüsselwortswitch
, gefolgt von einem Klammerausdruck (als Switchausdruck bezeichnet), gefolgt von einer 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). Jedes switch_label, das case
enthält, hat ein zugehöriges Muster (§11), gegen das der Wert des switch-Ausdrucks 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.
Der kontrollierende Typ einer switch
Anweisung wird durch den switch-Ausdruck festgelegt.
- Wenn der Typ des switch-Ausdrucks
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,char
,bool
,string
oder ein enum_type ist, oder wenn es sich um den Typ eines nullbaren Wertes handelt, der einem dieser Typen entspricht, dann ist dies der bestimmende Typ derswitch
-Anweisung. - Andernfalls, wenn genau eine benutzerdefinierte implizite Konvertierung vom Typ des Switch-Ausdrucks in einen der folgenden möglichen Regeltypen vorhanden ist:
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,char
,string
oder einem Nullwerttyp, der einem dieser Typen entspricht, dann ist der konvertierte Typ der Regeltyp derswitch
-Anweisung. - Andernfalls ist der bestimmende Typ der
switch
-Anweisung der Typ des switch-Ausdrucks. 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:
- Der switch-Ausdruck wird ausgewertet und in den kontrollierenden Typ umgewandelt.
- Die Steuerung wird entsprechend dem Wert des umgewandelten switch-Ausdrucks übertragen:
- Das lexikalisch erste Muster im Set von
case
Kennzeichnungen in derselbenswitch
Anweisung, das mit dem Wert des switch-Ausdrucks übereinstimmt und für das der Guard-Ausdruck entweder nicht vorhanden ist oder den Wert TRUE hat, bewirkt, dass die Kontrolle auf die Anweisungsliste übertragen wird, die auf die übereinstimmendecase
Kennzeichnung folgt. - Andernfalls, wenn eine
default
Kennzeichnung vorhanden ist, wird die Kontrolle auf die Anweisungsliste übertragen, die auf diedefault
Kennzeichnung folgt. - Andernfalls wird die Steuerung an den Endpunkt der
switch
Anweisung übertragen.
- Das lexikalisch erste Muster im Set 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
true
ausgewertet 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 case
odergoto default
erklä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
break
Anweisungen 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 case
odergoto default
Anweisung, aber jedes Konstrukt, das den Endpunkt der Anweisungsliste unerreichbar macht, ist erlaubt. Beispielsweise ist einewhile
Anweisung, die vom booleschen Ausdrucktrue
gesteuert wird, bekannt, dass sie nie ihren Endpunkt erreicht. Ebenso überträgt einethrow
oderreturn
Anweisung 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
switch
Anweisung kann der Typstring
sein. 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: Ähnlich den Gleichheitsoperatoren für Zeichenketten (§12.12.8) ist die
switch
-Anweisung schreibempfindlich und führt einen gegebenen Switch-Abschnitt nur aus, wenn der Ausdruck der Switch-Zeichenkette exakt mit einercase
-Beschriftungskonstante übereinstimmt. end note Wenn der Governance-Typ einerswitch
Anweisungstring
oder ein nullbarer Wertetyp ist, ist der Wertnull
alscase
gekennzeichnete Konstante 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:
- Der switch-Ausdruck ist ein konstanter Wert und entweder
- 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
default
Kennzeichnung, 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
- Der switch-Ausdruck ist kein konstanter Wert und entweder
- Die Kennzeichnung ist ein
case
ohne Guard oder mit einem Guard, dessen Wert nicht die Konstante false ist; oder - Es ist eine
default
Kennzeichnung 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
null
passen würde.
- Die Kennzeichnung ist ein
- Die Kennzeichnung switch wird durch eine erreichbare
goto case
odergoto default
Anweisung 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
switch
Anweisung enthält eine erreichbarebreak
Anweisung, die dieswitch
Anweisung verlässt. - Es ist keine
default
Kennzeichnung vorhanden und entweder- Der switch-Ausdruck ist ein nicht konstanter Wert und das Set der Muster, die in 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 Steuerungstyp switch.
- Der switch-Ausdruck ist ein nicht-konstanter Wert eines löschbaren Typs, und kein Muster, das unter den Fällen der switch-Anweisung auftaucht, die keine Guards haben oder Guards haben, deren Wert die Konstante true ist, würde auf den Wert
null
passen. - Der switch-Ausdruck ist ein konstanter Wert und keine
case
Kennzeichnung ohne Guard oder deren Guard die Konstante true ist, würde auf diesen Wert passen.
Beispiel: Der folgende Code zeigt eine prägnante Verwendung der
when
Klausel: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
null
der 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.24) 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 einercontinue
Anweisung), wird das Steuerelement an den Anfang derwhile
Anweisung übertragen. - Wenn der boolesche Ausdruck ergibt
false
, wird das Steuerelement an den Endpunkt derwhile
Anweisung ü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 false
aufweist.
Der Endpunkt einer while
Anweisung ist erreichbar, wenn mindestens einer der folgenden Werte zutrifft:
- Die
while
Anweisung enthält eine erreichbarebreak
Anweisung, die diewhile
Anweisung verlässt. - Die
while
Anweisung 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
continue
Anweisung) erreicht, wird die boolean_expression (§12.24) ausgewertet. Wenn der boolesche Ausdruck ergibttrue
, wird das Steuerelement an den Anfang derdo
Anweisung übertragen. Andernfalls wird die Steuerung an den Endpunkt derdo
Anweisung ü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
do
Anweisung enthält eine erreichbarebreak
Anweisung, die diedo
Anweisung 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.24).
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
true
ergibt, wird die Kontrolle an die eingebettete Anweisung übertragen. Wenn und wenn das Steuerelement den Endpunkt der eingebetteten Anweisung (möglicherweise von der Ausführung einercontinue
Anweisung) 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 derfor
Anweisung ü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
for
Anweisung ist erreichbar und es ist keine for_condition vorhanden. - Die
for
Anweisung ist erreichbar, und es ist eine for_condition vorhanden, die nicht den konstanten Wertfalse
aufweist.
Der Endpunkt einer for
Anweisung ist erreichbar, wenn mindestens einer der folgenden Werte zutrifft:
- Die
for
Anweisung enthält eine erreichbarebreak
Anweisung, die diefor
Anweisung verlässt. - Die
for
Anweisung ist erreichbar, und es ist eine for_condition vorhanden, die nicht den konstanten Werttrue
aufweist.
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, den Enumerator-Typ und den Iterationstyp des Ausdrucks. Die Verarbeitung für eine foreach
-Erklärung ist in §13.9.5.2 detailliert beschrieben, und der Prozess für eine await foreach
-Erklärung ist in §13.9.5.3 beschrieben.
Anmerkung: Wenn Ausdruck den Wert
null
hat, wird zur Laufzeit einSystem.NullReferenceException
ausgelöst. Hinweisende
Eine Implementierung darf eine bestimmte foreach_statement anders implementieren, z. B. aus Leistungsgründen, solange das Verhalten mit der oben genannten Erweiterung konsistent ist.
13.9.5.2 Synchrone Foreach
Die Kompilierungszeitverarbeitung einer foreach
Anweisung bestimmt zuerst den Auflistungstyp, den Enumerator-Typ und den Iterationstyp des Ausdrucks. Diese Festlegung erfolgt wie folgt:
- Wenn der Ausdruckstyp
X
ein Arraytyp ist, gibt es eine implizite Verweiskonvertierung von X in dieIEnumerable
Schnittstelle (daSystem.Array
diese Schnittstelle implementiert wird). Der Sammlungstyp ist dieIEnumerable
Schnittstelle, der Enumerationstyp ist dieIEnumerator
Schnittstelle und der Iterationstyp ist der Elementtyp des ArraytypsX
. - Wenn der Typ
X
von Ausdruckdynamic
ist, dann gibt es eine implizite Konvertierung von Ausdruck in dieIEnumerable
Schnittstelle (§10.2.10). Der Sammlungstyp ist dieIEnumerable
Schnittstelle, und der Enumerationstyp ist dieIEnumerator
Schnittstelle. Wenn dervar
Bezeichner als local_variable_type angegeben wird, ist der Iterationstypdynamic
. Andernfalls ist erobject
. - Ermitteln Sie andernfalls, ob der Typ
X
über eine geeigneteGetEnumerator
Methode verfügt:- Führen Sie die Mitgliedersuche für den Typ
X
mit dem BezeichnerGetEnumerator
und ohne Typargumente durch. Wenn die Mitgliederabfrage entweder keine Übereinstimmung ergibt oder zu einer Mehrdeutigkeit führt oder zu einer Übereinstimmung führt, die keine Methodengruppe ist, sollten Sie nach einer aufzählbaren Schnittstelle suchen, wie im Folgenden 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
E
derGetEnumerator
Methode keine Klasse, Struktur oder Schnittstellentyp ist, wird ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt. - Membersuche wird bei
E
mit dem BezeichnerCurrent
und ohne Typargumente durchgeführt. Wenn die Membersuche keine Übereinstimmung ergibt, das Ergebnis ein Fehler ist oder es sich bei dem Ergebnis nicht um eine öffentliche Instanz-Eigenschaft handelt, die das Lesen zulässt, wird ein Fehler erzeugt, und es werden keine weiteren Schritte unternommen. - Membersuche wird bei
E
mit dem BezeichnerMoveNext
und ohne Typargumente durchgeführt. Wenn die Membersuche keine Übereinstimmung produziert, führt dies zu einem Fehler oder wenn das Ergebnis etwas anderes als eine Methodengruppe ist, führt dies zu einem Fehler, und es werden keine weiteren Schritte unternommen. - Die Auflösung von Überladungen wird für die Methodengruppe mit einer leeren Argumentliste durchgeführt. Wenn die Überladungsauflösung zu keiner anwendbaren Methode führt, zu Mehrdeutigkeit oder einer einzigen besten Methode führt, aber diese Methode entweder statisch oder nicht öffentlich ist, oder ihr Rückgabewert nicht
bool
ist, wird ein Fehler erzeugt und es werden keine weiteren Schritte ausgeführt. - Der Auflistungstyp ist
X
, der Enumerationstyp istE
, und der Iterationstyp ist der Typ derCurrent
Eigenschaft. DieCurrent
-Eigenschaft kann denref
-Modifikator enthalten. In diesem Fall ist der zurückgegebene Ausdruck eine variable_reference (§9.5), die optional schreibgeschützt ist.
- Führen Sie die Mitgliedersuche für den Typ
- Suchen Sie andernfalls nach einer aufzählbaren Schnittstelle:
- Wenn unter allen Typen
Tᵢ
, für die es eine implizite Konvertierung vonX
zuIEnumerable<Tᵢ>
gibt, ein eindeutiger TypT
existiert, wobeiT
nichtdynamic
ist, und für alle anderenTᵢ
eine implizite Konvertierung vonIEnumerable<T>
zuIEnumerable<Tᵢ>
besteht, dann ist der Sammlungstyp die SchnittstelleIEnumerable<T>
, der Enumeratortyp die SchnittstelleIEnumerator<T>
und der IterationstypT
. - Andernfalls wird, wenn mehr als ein solcher Typ
T
vorhanden ist, ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt. - Andernfalls, wenn es eine implizite Konversion von
X
zur SchnittstelleSystem.Collections.IEnumerable
gibt, ist der Typ der Collection diese Schnittstelle, der Typ des Aufzählers ist die SchnittstelleSystem.Collections.IEnumerator
und der Typ der Iteration istobject
. - Andernfalls wird ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt.
- Wenn unter allen Typen
Die obigen Schritte erzeugen, falls erfolgreich, eindeutig einen Sammlungstyp, Enumeratortyp C
E
und Iterationstyp T
, ref T
oder ref readonly T
. 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, aber v
darf nicht neu zugewiesen werden (§12.21.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 von v
innerhalb der while
-Schleife ist wichtig für die Art und Weise, wie sie von jeder anonymen Funktion, die in der embedded_statement auftritt, erfasst wird (§12.19.6.2).
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
v
in der erweiterten Form außerhalb derwhile
Schleife deklariert würde, würde sie für alle Iterationen freigegeben, und ihr Wert nach derfor
Schleife wäre der endwert,13
was der Aufruf derf
Schleife drucken würde. Da jede Iteration über ihre eigene Variablev
verfügt, wird die in der ersten Iteration erfasste Variablef
weiterhin den Wert7
halten, der ausgedruckt wird. (Beachten Sie, dass frühere Versionen von C#v
außerhalb derwhile
-Schleife deklarierten.)Endbeispiel
Der Inhalt des finally
Blocks wird in den folgenden Schritten gemäß erstellt:
Wenn es eine implizite Konvertierung von
E
zurSystem.IDisposable
Schnittstelle gibt, dannWenn
E
ein nicht-nullbarer Werttyp ist, wird diefinally
-Klausel zum semantischen Äquivalent von erweitert:finally { ((System.IDisposable)e).Dispose(); }
Andernfalls wird die Klausel
finally
auf das semantische Äquivalent des Folgenden erweitert:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }
Wenn
E
jedoch ein Werttyp ist oder ein Typparameter, der zu einem Werttyp instanziiert wurde, verursacht die Konvertierung vone
inSystem.IDisposable
kein Boxing.
Wenn es sich bei
E
um einen versiegelten Typ handelt, wird diefinally
-Klausel andernfalls auf einen leeren Block erweitert.finally {}
Andernfalls wird die
finally
Klausel 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.9
Endbeispiel
Beispiel: Im folgenden Beispiel
int[] numbers = { 1, 3, 5, 7, 9 }; foreach (var n in numbers) { Console.WriteLine(n); }
Der Typ von
n
wird aufint
abgeleitet, der Iterationstyp vonnumbers
.Endbeispiel
13.9.5.3 await foreach
Die Kompilierungszeitverarbeitung einer foreach
Anweisung bestimmt zuerst den Auflistungstyp, den Enumerator-Typ und den Iterationstyp des Ausdrucks. Die Verarbeitung für eine foreach
-Erklärung ist in §13.9.5.2 detailliert beschrieben, und der Prozess für eine await foreach
-Erklärung ist in §13.9.5.3 beschrieben.
Diese Festlegung erfolgt wie folgt:
- Ermitteln Sie, ob der Typ
X
über eine geeigneteGetAsyncEnumerator
Methode verfügt:- Führen Sie die Mitgliedersuche für den Typ
X
mit dem BezeichnerGetAsyncEnumerator
und ohne Typargumente durch. Wenn die Mitgliederabfrage entweder keine Übereinstimmung ergibt oder zu einer Mehrdeutigkeit führt oder zu einer Übereinstimmung führt, die keine Methodengruppe ist, sollten Sie nach einer aufzählbaren Schnittstelle suchen, wie im Folgenden 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
E
derGetAsyncEnumerator
Methode keine Klasse, Struktur oder Schnittstellentyp ist, wird ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt. - Membersuche wird bei
E
mit dem BezeichnerCurrent
und ohne Typargumente durchgeführt. Wenn die Membersuche keine Übereinstimmung ergibt, das Ergebnis ein Fehler ist oder es sich bei dem Ergebnis nicht um eine öffentliche Instanz-Eigenschaft handelt, die das Lesen zulässt, wird ein Fehler erzeugt, und es werden keine weiteren Schritte unternommen. - Membersuche wird bei
E
mit dem BezeichnerMoveNextAsync
und ohne Typargumente durchgeführt. Wenn die Membersuche keine Übereinstimmung produziert, führt dies zu einem Fehler oder wenn das Ergebnis etwas anderes als eine Methodengruppe ist, führt dies zu einem Fehler, und es werden keine weiteren Schritte unternommen. - Die Auflösung von Überladungen wird für die Methodengruppe mit einer leeren Argumentliste durchgeführt. 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, oder der Rückgabetyp kann nicht erwartet werden (§12.9.8.2), wenn die await_expression als ein
bool
(§12.9.8.3) klassifiziert wird, wird ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt. - Der Auflistungstyp ist
X
, der Enumerationstyp istE
, und der Iterationstyp ist der Typ derCurrent
Eigenschaft.
- Führen Sie die Mitgliedersuche für den Typ
- Suchen Sie andernfalls nach einer asynchronen enumerationsfähigen Schnittstelle:
- Wenn unter allen Typen
Tᵢ
, für die es eine implizite Konvertierung vonX
zuIAsyncEnumerable<Tᵢ>
gibt, ein eindeutiger TypT
existiert, wobeiT
nichtdynamic
ist, und für alle anderenTᵢ
eine implizite Konvertierung vonIAsyncEnumerable<T>
zuIAsyncEnumerable<Tᵢ>
besteht, dann ist der Sammlungstyp die SchnittstelleIAsyncEnumerable<T>
, der Enumeratortyp die SchnittstelleIAsyncEnumerator<T>
und der IterationstypT
. - Andernfalls wird, wenn mehr als ein solcher Typ
T
vorhanden ist, ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt. - Andernfalls wird ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt.
- Wenn unter allen Typen
Die oben genannten Schritte führen, wenn erfolgreich, eindeutig zu einem Sammlungstyp C
, Enumeratortyp E
und Iterationstyp T
. Eine await foreach
Anweisung der Form
await foreach (V v in x) «embedded_statement»
ist dann gleichbedeutend mit:
{
E e = ((C)(x)).GetAsyncEnumerator();
try
{
while (await e.MoveNextAsync())
{
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 await foreach
Anweisung) vorliegt, wird ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt.
Ein asynchroner Enumerator kann optional eine DisposeAsync
-Methode zur Verfügung stellen, die ohne Argumente aufgerufen werden kann und die etwas zurückgibt, das await
gefragt werden kann und dessen GetResult()
Rückgabe void
ist.
Eine foreach
-Anweisung der Form
await foreach (T item in enumerable) «embedded_statement»
wird erweitert auf:
var enumerator = enumerable.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
T item = enumerator.Current;
«embedded_statement»
}
}
finally
{
await enumerator.DisposeAsync(); // omitted, along with the try/finally,
// if the enumerator doesn't expose DisposeAsync
}
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
finally
Blöcke, die mit zweitry
Anweisungen 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 break
Endbeispiel
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
, for
oder 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 diefinally
Blöcke aller dazwischen liegendentry
Anweisungen ausgeführt wurden. - Die Steuerung wird an das Ziel der
break
Anweisung ü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
, for
oder 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 diefinally
Blöcke aller dazwischen liegendentry
Anweisungen ausgeführt wurden. - Die Steuerung wird an das Ziel der
continue
Anweisung ü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 goto
Kennzeichnung-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
goto
Anweisung, 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 diefinally
Blöcke aller dazwischen liegendentry
Anweisungen ausgeführt wurden. - Die Steuerung wird an das Ziel der
goto
Anweisung ü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 ref
Ausdruck 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 Body von anonymen Funktionsausdrücken verwendet werden (§12.19) und sind an der Bestimmung der Konversionen beteiligt, die für diese Funktionen existieren (§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
readonly
enthält, ist die resultierende Variable schreibgeschützt. - Wenn die
return
Anweisung von einem oder mehrerentry
catch
Blöcken mit zugeordnetenfinally
Blöcken eingeschlossen wird, wird die Steuerung zunächst in 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 so lange wiederholt, bis diefinally
Blockierungen aller einschließendentry
Anweisungen 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 (§21.4) angegeben.
Im aktuellen Funktionsmitglied wird jede
try
-Anweisung untersucht, die den Wurfpunkt einschließt. Für jede AnweisungS
, beginnend mit der innerstentry
Anweisung und endet mit der äußerstentry
Anweisung, werden die folgenden Schritte ausgewertet:- Wenn der
try
-Block vonS
den Übergabepunkt einschließt und wennS
eine 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 AusnahmetypT
bezeichnet) angibt, sodass sich der LaufzeittypE
vonT
ableitet, 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 zutrue
ausgewertet 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
try
Block oder eincatch
Block vonS
den Wurfpunkt umschließt undS
über einenfinally
Block verfügt, wird die Kontrolle an denfinally
Block ü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 desfinally
Blocks 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.Exception
abgeleitetes Objekt dargestellt werden können, obwohl solche Ausnahmen niemals durch C#-Code generiert werden könnten. Eine allgemeinecatch
Klausel kann verwendet werden, um solche Ausnahmen abzufangen. Daher unterscheidet sich eine allgemeinecatch
Klausel semantisch von einem, der den TypSystem.Exception
angibt, 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
catch
Klauseln 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
F
fä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: G
Wenn der erste
catch
Blocke
ausgelö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: F
Endbeispiel
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 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
Method
löst eine Ausnahme aus. Die erste Aktion besteht darin, die eingeschlossenencatch
Klauseln zu untersuchen und alle Ausnahmefilter auszuführen. ** Anschließend wird diefinally
-Klausel inMethod
ausgeführt, bevor die Steuerung an die umschließende passendecatch
-Klausel übertragen wird. Die resultierende Ausgabe ist:Filter Finally Catch
Endbeispiel
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
try
Blocks ist erreichbar oder der Endpunkt mindestens einescatch
Blocks ist erreichbar. - Wenn ein
finally
Block vorhanden ist, ist der Endpunkt desfinally
Blocks 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 lock
Anweisung 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
Die using
Anweisung ruft eine oder mehrere Ressourcen ab, führt eine Anweisung aus und entsorgt dann die Ressource.
using_statement
: 'using' '(' resource_acquisition ')' embedded_statement
;
resource_acquisition
: local_variable_declaration
| expression
;
Eine Ressource ist eine Klasse oder Struktur, die die System.IDisposable
Schnittstelle (IAsyncDisposable
für asynchrone Datenströme) implementiert, die eine einzelne parameterlose Methode namens Dispose
(DisposeAsync
für asynchrone Datenströme) enthält. Code, das eine Ressource verwendet, kann Dispose
aufrufen, um anzugeben, 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 Typ sein, der implizit in (System.IDisposable
für asynchrone IAsyncDisposable
Datenströme) konvertiert werden kann. Wenn die Form von resource_acquisition ein Ausdruck ist, dann muss dieser Ausdruck implizit in System.IDisposable
konvertierbar sein (IAsyncDisposable
für asynchrone Datenströme).
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 eine null
Ressource abgerufen wird, wird kein Aufruf von Dispose
(DisposeAsync
für asynchrone Datenströme) 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
(IAsyncDisposable
für asynchrone Datenströme) 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 Erweiterungen. Wenn ResourceType
es sich um einen nicht nullablen Werttyp oder einen Typparameter mit der Werttypeinschränkung (§15.2.5) handelt, entspricht die Erweiterung semantisch dem
{
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.
Andernfalls, wenn ResourceType
dynamic
ist, lautet die Erweiterung
{
ResourceType resource = «expression»;
IDisposable d = resource;
try
{
«statement»;
}
finally
{
if (d != null)
{
d.Dispose();
}
}
}
Andernfalls ist die Erweiterung
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IDisposable d = (IDisposable)resource;
if (d != null)
{
d.Dispose();
}
}
}
In jeder Erweiterung ist die resource
-Variable in der eingebetteten Anweisung schreibgeschützt und die d
-Variable ist in der eingebetteten Anweisung unzugänglich und unsichtbar für sie.
Eine Implementierung darf eine bestimmte using_statement unterschiedlich implementieren, z. B. aus Leistungsgründen, solange das Verhalten mit der oben genannten Erweiterung konsistent ist.
Eine using
-Anweisung in der Form:
using («expression») «statement»
hat die gleichen drei möglichen Erweiterungen. In diesem Fall ResourceType
handelt es sich implizit um den Kompilierungszeittyp des Ausdrucks, sofern er über einen verfügt. Andernfalls wird die Schnittstelle IDisposable
(IAsyncDisposable
für asynchrone Datenströme) selbst verwendet als ResourceType
. Auf die resource
Variable kann nicht zugegriffen werden, und die eingebettete Anweisung ist nicht sichtbar.
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
TextWriter
undTextReader
die SchnittstelleIDisposable
implementieren, kann das Beispielusing
Anweisungen verwenden, um sicherzustellen, dass die zugrunde liegende Datei nach den Schreib- oder Leseoperationen ordnungsgemäß abgeschlossen wird.Endbeispiel
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
yield
Anweisung (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 return
Anweisung an einer beliebigen Stelle in einertry
Anweisung erscheint, die irgendwelche catch_clauses enthält.
Beispiel: Das folgende Beispiel zeigt einige gültige und ungültige Verwendungen von
yield
Anweisungen.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
Current
Eigenschaft des Enumeratorobjekts zugewiesen. - Die Ausführung des Iterator-Blocks wird unterbrochen. Wenn sich die
yield return
Anweisung in einem odertry
mehreren Blöcken befindet, werden die zugehörigenfinally
Blöcke zurzeit nicht ausgeführt. - Die Methode
MoveNext
des Aufzählungsobjekts gibttrue
an 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 break
Anweisung von einem odertry
mehreren Blöcken mit zugeordnetenfinally
Blöcken eingeschlossen wird, wird das Steuerelement zunächst in 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 so lange wiederholt, bis diefinally
Blockierungen aller einschließendentry
Anweisungen ausgeführt wurden. - Die Kontrolle wird an den Aufrufer des Iteratorblocks zurückgegeben. Dies ist entweder die
MoveNext
Methode oderDispose
Methode 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