13 Aussagen
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. Endnote
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 embedded_statement nonterminal wird für Anweisungen verwendet, die in anderen Anweisungen angezeigt werden. Die Verwendung von embedded_statement anstelle von Anweisungen schließt die Verwendung 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 Kompilierungsfehler, da eine
if
Anweisung eine embedded_statement anstelle einer Anweisung für dieif
Verzweigung erfordert. 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 Anweisung nicht erreichbar ist.
Hinweis: Um festzustellen, ob eine bestimmte Anweisung oder ein bestimmter Endpunkt erreichbar ist, führt der Compiler eine Flussanalyse gemäß den für jede Anweisung definierten Reichweitenregeln aus. 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 gilt für die Steuerungsflussanalyse ein nicht konstanter Ausdruck eines bestimmten Typs als möglicher Wert dieses Typs.
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 zur Kompilierung ausgewertet wird, wird der Wertfalse
erzeugt, derConsole.WriteLine
Aufruf wird als nicht erreichbar betrachtet. Wenni
es sich jedoch um eine lokale Variable handeltvoid 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.Endnote
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 sich um einen Kompilierungszeitfehler handelt, damit der Endpunkt einer Anweisung erreichbar ist:
Da die
switch
Anweisung keinen Schalterabschnitt in den nächsten Schalterabschnitt "fallen" lässt, handelt es sich um einen Kompilierungszeitfehler für den Endpunkt der Anweisungsliste eines Switchabschnitts, der erreichbar sein kann. 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 statement_list (§13.3.2), die in geschweifte Klammern eingeschlossen ist. Wenn die Anweisungsliste nicht angegeben wird, wird der Block als leer angegeben.
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 das Steuerelement in die Anweisungsliste übertragen. Wenn und wenn das Steuerelement den Endpunkt der Anweisungsliste erreicht, wird das Steuerelement 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.14) 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 treten in Block s (§13.3) und in switch_blocks (§13.8.3) auf.
statement_list
: statement+
;
Eine Anweisungsliste wird ausgeführt, indem die Steuerung an 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 Werte zutrifft:
- Die Anweisung ist die erste Anweisung, und die Anweisungsliste selbst ist erreichbar.
- Der Endpunkt der vorherigen Anweisung ist erreichbar.
- Die Anweisung ist eine beschriftete Anweisung, und auf die Bezeichnung wird durch eine erreichbare
goto
Anweisung verwiesen.
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 führt nichts aus.
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()) ; }
Außerdem kann eine leere Anweisung verwendet werden, um eine Bezeichnung direkt vor dem Schließen von "
}
" eines Blocks zu deklarieren:void F(bool done) { ... if (done) { goto exit; } ... exit: ; }
Endbeispiel
13.5 Bezeichnungsanweisungen
Mit einem labeled_statement kann eine Anweisung einer Bezeichnung vorangestellt werden. Beschriftete Anweisungen sind in Blöcken zulässig, sind jedoch nicht als eingebettete Anweisungen zulässig.
labeled_statement
: identifier ':' statement
;
Eine beschriftete Anweisung deklariert eine Bezeichnung mit dem vom Bezeichner angegebenen Namen. Der Bereich einer Bezeichnung ist der gesamte Block, in dem die Bezeichnung deklariert wird, einschließlich aller geschachtelten Blöcke. Es handelt sich um einen Kompilierungszeitfehler für zwei Bezeichnungen mit demselben Namen, um überlappende Bereiche zu haben.
Auf eine Bezeichnung kann innerhalb des Bereichs der Bezeichnung aus Anweisungen (§13.10.4) verwiesen goto
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. Endnote
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 Erklärungsanweisungen
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 Blöcken und Switchblöcken zulässig, sind jedoch nicht als eingebettete Anweisungen zulässig.
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 das nächste eingeschlossene Deklarationszeichen (§7.3) eingeführt.
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 kein Typ im Bereich benannt
var
ist und die Eingabe mit implicitly_typed_local_variable_declaration übereinstimmt, wird er ausgewählt; - Andernfalls gilt ein benannter
var
Typ im Bereich, implicitly_typed_local_variable_declaration nicht als mögliche Übereinstimmung betrachtet wird.
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 mehrere Deklaratoren in einer Deklaration vorhanden sind, werden sie verarbeitet, einschließlich aller Initialisierungsausdrücke, um von links nach rechts (§9.4.4.5) zu wechseln.
Hinweis: Für eine local_variable_declaration nicht als for_initializer (§13.9.4) oder resource_acquisition (§13.14) entspricht dieser links-nach-rechts-Reihenfolge jedem Deklarator innerhalb eines separaten local_variable_declaration. Zum Beispiel:
void F() { int x = 1, y, z = x * 2; }
entspricht:
void F() { int x = 1; int y; int z = x * 2; }
Endnote
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, ist der Bereich die for_initializer, for_condition, for_iterator und embedded_statement (§13.9.4);
- Wenn die Deklaration als resource_acquisition erfolgt, ist der Bereich der äußersten Block der semantisch gleichwertigen Erweiterung der using_statement (§13.14);
- Andernfalls ist der Bereich der Block, in dem die Deklaration auftritt.
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-context (§9.7.2) einer lokalen Referenzvariable ist der ref-safe-context seiner initialisierenden variable_reference. Der bezugssichere Kontext lokaler Variablen ist Deklarationsblock.
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 Kompilierungszeittyp aufweisen. T
Die erste Alternative deklariert eine Variable mit einem Anfangswert des Ausdrucks. Der Typ ist T?
ein T
nicht nullabler Bezugstyp, andernfalls ist T
der Typ . Die zweite Alternative deklariert eine Bezugsvariable mit einem Anfangswert von ref
variable_reference. Der Typ ist ref T?
ein T
Nicht-Null-Bezugstyp, andernfalls ist ref T
der Typ . (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 typierte Deklarationen der lokalen Variablen
explicitly_typed_ref_local_variable_declaration
: ref_kind type ref_local_variable_declarators
;
ref_local_variable_declarators
: ref_local_variable_declarator (',' ref_local_variable_declarator)*
;
ref_local_variable_declarator
: identifier '=' 'ref' variable_reference
;
Die initialisierende variable_reference muss Typ aufweisen und die gleichen Anforderungen erfüllen wie für eine Refzuordnung (§12.21.3).
Wenn ref_kind ist ref readonly
, sind die deklarierten Bezeichner Verweise auf Variablen, die schreibgeschützt behandelt werden. Andernfalls, wenn ref_kind ist ref
, sind die deklarierten Bezeichner Verweise auf Variablen, die schreibbar sein müssen.
Es handelt sich um einen Kompilierungsfehler, um eine lokale Referenzvariable oder eine Variable eines ref struct
Typs innerhalb einer methode zu deklarieren, die mit dem method_modifier async
deklariert wurde, oder innerhalb eines Iterators (§15.14).
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 ';'
;
Grammatikhinweis: Bei der Erkennung einer local_function_body , wenn sowohl die alternativen null_conditional_invocation_expression als auch die Ausdrucksalternativen anwendbar sind, wird der erste Ausgewählt. (§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 alle Ausnahmen nur beobachtet, wenn der zurückgegebene Vorgang erwartet 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 eines local_function_declaration muss in seinem deklarierten Blockbereich eindeutig sein, einschließlich aller eingeschlossenen lokalen Variablendeklarationszeichen. Eine Folge davon ist, dass überladene local_function_declarations nicht zulässig sind.
Ein local_function_declaration kann einen async
(§15.15)-Modifizierer und einen unsafe
(§23.1)-Modifizierer enthalten. Wenn die Deklaration den async
Modifizierer enthält, muss der Rückgabetyp oder ein «TaskType»
Typ (§15.15.1) seinvoid
. 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 handelt sich um einen Kompilierungszeitfehler für type_parameter_list oder parameter_list, die Attribute enthalten sollen. 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 Blockbereich deklariert. Eine nicht statische lokale Funktion kann Variablen aus dem eingeschlossenen Bereich erfassen, während eine statische lokale Funktion nicht (sodass sie keinen Zugriff auf das Einschließen von Lokalen, Parametern, nicht statischen lokalen Funktionen oder this
) hat. 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. Der Compiler bestimmt, welche Variablen definitiv zur Rückgabe zugewiesen werden (§9.4.4.33).
Wenn der Typ eines this
Strukturtyps ist, handelt es sich um einen Kompilierungszeitfehler für den Textkörper einer lokalen Funktion, auf die zugegriffen this
werden kann. Dies gilt, ob der Zugriff explizit (wie in this.x
) oder implizit ist (wie in x
der Stelle ein x
Instanzmemm der Struktur). Diese Regel verbietet nur diesen Zugriff und wirkt sich nicht darauf aus, ob die Elementsuche zu einem Mitglied der Struktur führt.
Es handelt sich um einen Kompilierungszeitfehler für den Textkörper der lokalen Funktion, um eine goto
Anweisung, eine break
Anweisung oder eine continue
Anweisung zu enthalten, deren Ziel außerhalb des Textkörpers der lokalen Funktion liegt.
Hinweis: die oben genannten Regeln für
goto
this
anonyme Funktionen in §12.19.3. Endnote
Eine lokale Funktion kann vor der Deklaration von einem lexikalischen Punkt aufgerufen werden. Es handelt sich jedoch um einen Kompilierungszeitfehler, damit die Funktion lexikalisch vor der Deklaration einer Variablen deklariert wird, die in der lokalen Funktion verwendet wird (§7.7).
Es ist ein Kompilierungszeitfehler für eine lokale Funktion, um einen Parameter, Typparameter oder lokale Variable mit demselben Namen zu deklarieren wie eine, die in jedem eingeschlossenen lokalen Variablendeklarationsbereich 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 einer Ausdrucksstruktur verwendet werden.
Eine statische lokale Funktion
- Kann auf statische Elemente, Typparameter, Konstantendefinitionen und statische lokale Funktionen aus dem eingeschlossenen Bereich verweisen.
- Verweist weder auf Elemente aus einem impliziten
this
Verweis nochbase
aufthis
lokale Variablen, Parameter oder nicht statische lokale Funktionen aus dem eingeschlossenen Bereich. Alle diese sind jedoch in einemnameof()
Ausdruck zulässig.
13.7 Ausdrucksanweisungen
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. Endnote
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 Selection-Anweisungen
13.8.1 Allgemein
Selection-Anweisungen wählen eine der möglichen Anweisungen für die Ausführung basierend auf dem Wert eines Ausdrucks 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
Teil ist dem lexikalisch nächsten vorangehenden if
Teil zugeordnet, der von der Syntax zulässig ist.
Beispiel: Eine
if
Anweisung des Formularsif (x) if (y) F(); else G();
für die folgende Syntax:
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 ergibt
false
und einelse
Teil vorhanden ist, wird das Steuerelement an 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 ergibt
false
und einelse
Teil nicht vorhanden ist, wird das Steuerelement 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 für die Ausführung einer Anweisungsliste mit einer zugeordneten Switchbeschriftung aus, die dem Wert des Switchausdrucks 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. Die switch_block besteht aus null oder mehr switch_section s, die in geschweifteKlammern eingeschlossen sind. Jede switch_section besteht aus einem oder mehreren switch_labelgefolgt von einem statement_list (§13.3.2). Jede switch_label enthält case
ein zugeordnetes Muster (§11), mit dem der Wert des Schalterausdrucks 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 Regeltyp einer switch
Anweisung wird durch den Switch-Ausdruck festgelegt.
- Wenn der Typ des Switchausdrucks den Wert ,
sbyte
, ,short
,ushort
,int
,char
ulong
long
string
uint
bool
oder ein enum_type, oder wenn es sich um den Nullwerttyp handelt, der einem dieser Typen entspricht, dann ist dies der Steuerungstyp derswitch
Anweisung.byte
- Andernfalls ist genau eine benutzerdefinierte implizite Konvertierung vom Typ des Switchausdrucks in einen der folgenden möglichen Regeltypen vorhanden:
sbyte
, ,byte
,short
,ushort
ulong
int
long
char
uint
oderstring
, ein Nullwerttyp, der einem dieser Typen entspricht, dann ist der konvertierte Typ der Anweisung der Regeltyp.switch
- Andernfalls ist der Steuerungstyp der
switch
Anweisung der Typ des Switch-Ausdrucks. Es handelt sich um einen Fehler, wenn kein solcher Typ vorhanden ist.
Es kann höchstens eine default
Bezeichnung in einer switch
Anweisung geben.
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 Switchbezeichnung von (§11.3) den Satz von Mustern früherer Switchbeschriftungen der Switch-Anweisung subsumiert wird, die keinen Case Guard besitzen oder dessen 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 Schalterausdruck wird ausgewertet und in den Steuerungstyp konvertiert.
- Das Steuerelement wird entsprechend dem Wert des konvertierten Schalterausdrucks übertragen:
- Das lexikalische erste Muster in der Gruppe von
case
Bezeichnungen in derselbenswitch
Anweisung, die dem Wert des Switch-Ausdrucks entspricht und für das der Guard-Ausdruck entweder nicht vorhanden ist oder als wahr ausgewertet wird, bewirkt, dass das Steuerelement nach der übereinstimmendencase
Bezeichnung in die Anweisungsliste übertragen wird. - Andernfalls wird das Steuerelement, wenn eine
default
Bezeichnung vorhanden ist, in die Anweisungsliste nach derdefault
Bezeichnung übertragen. - Andernfalls wird die Steuerung an den Endpunkt der
switch
Anweisung übertragen.
- Das lexikalische erste Muster in der Gruppe von
Hinweis: Die Reihenfolge, in der Muster zur Laufzeit abgeglichen werden, ist nicht definiert. Ein Compiler ist zulässig (aber nicht erforderlich), Muster nicht in der Reihenfolge abzugleichen und die Ergebnisse bereits übereinstimmender Muster wiederzuverwenden, um das Ergebnis des Abgleichs anderer Muster zu berechnen. Dennoch ist der Compiler erforderlich, um das lexikalische erste Muster zu bestimmen, das dem Ausdruck entspricht und für das die Guard-Klausel entweder nicht vorhanden ist oder ausgewertet
true
wird. Endnote
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 Switchabschnitts nicht in den nächsten Switchabschnitt fallen, 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 Bezeichnungen 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 Fall durch", da die Bezeichnungen
case 2:
default:
Teil derselben switch_section sind.Endbeispiel
Hinweis: Die Regel "Kein Fall durch" verhindert eine allgemeine Klasse von Fehlern, die in C und C++ auftreten, wenn
break
Anweisungen versehentlich weggelassen werden. Beispielsweise können die Abschnitte derswitch
obigen Anweisung rückgängig gemacht werden, ohne das Verhalten der Anweisung zu beeinträchtigen:switch (i) { default: CaseAny(); break; case 1: CaseZeroOrOne(); goto default; case 0: CaseZero(); goto case 1; }
Endnote
Hinweis: Die Anweisungsliste eines Switch-Abschnitts endet in der Regel in einem
break
,goto case
odergoto default
einer Anweisung, aber jedes Konstrukt, das den Endpunkt der Anweisungsliste nicht erreichbar rendert, ist zulässig. 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; }
Endnote
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: Wie bei den Zeichenfolgengleichheitsoperatoren (§12.12.8) wird bei der Anweisung die
switch
Groß-/Kleinschreibung beachtet und nur dann ein gegebener Switch-Abschnitt ausgeführt, wenn die Switchausdruckzeichenfolge genau mit einercase
Bezeichnungskonstante übereinstimmt. end note When the governing type of aswitch
statement isstring
or a nullable value type, the valuenull
is permitted as acase
label constant.
Die statement_listeiner switch_block können Erklärungsanweisungen 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 Schalterausdruck ist ein konstanter Wert und eine der beiden
- die Bezeichnung ist ein
case
Muster, dessen Muster (§11.2.1) diesem Wert entspricht, und der Schutz der Bezeichnung ist entweder nicht vorhanden oder kein konstanter Ausdruck mit dem Wert "false"; oder - es handelt sich um eine
default
Beschriftung, und kein Switch-Abschnitt enthält eine Groß-/Kleinschreibungsbezeichnung, deren Muster diesem Wert entspricht und dessen Schutz entweder nicht vorhanden ist oder ein konstanter Ausdruck mit dem Wert true ist.
- die Bezeichnung ist ein
- Der Schalterausdruck ist kein konstanter Wert und beide
- die Beschriftung ist eine
case
ohne Schutz oder mit einem Guard, dessen Wert nicht die Konstante "false" ist; oder - es ist eine
default
Bezeichnung und- Der Satz von Mustern, die in den Fällen der Switch-Anweisung angezeigt werden, die keine Schutzvorrichtungen oder Wächter aufweisen, deren Wert die Konstante "true" ist, ist nicht erschöpfend (§11.4) für den Switch-Steuerungstyp; oder
- Der Switch-Steuerungstyp ist ein nullabler Typ und der Satz von Mustern, die in den Fällen der Switch-Anweisung angezeigt werden, die keine Schutzvorrichtungen oder Wächter aufweisen, deren Wert die Konstante true ist, enthält kein Muster, das mit dem Wert
null
übereinstimmt.
- die Beschriftung ist eine
- Auf die Switchbezeichnung wird durch eine erreichbare
goto case
odergoto default
anweisung verwiesen.
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
Bezeichnung vorhanden, und beides- Der Schalterausdruck ist ein nicht konstanter Wert, und der Satz von Mustern, die in den Fällen der Switch-Anweisung angezeigt werden, die keine Schutzvorrichtungen oder Wächter aufweisen, deren Wert die Konstante "true" ist, ist für den Switch-Regeltyp nicht erschöpfend (§11.4).
- Der Schalterausdruck ist ein nicht konstanter Wert eines nullfähigen Typs und kein Muster, das in den Fällen der Switch-Anweisung angezeigt wird, die keine Wächter oder Wächter aufweist, deren Wert die Konstante "true" ist, entspricht dem Wert
null
. - Der Schalterausdruck ist ein konstanter Wert und keine
case
Beschriftung ohne Schutz oder deren Schutz die Konstante "true" entspricht diesem Wert.
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 wiederholt eine eingebettete Anweisung aus.
iteration_statement
: while_statement
| do_statement
| for_statement
| foreach_statement
;
13.9.2 Die While-Anweisung
Die while
Anweisung führt eine eingebettete Anweisung 0 oder mehr Mal 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 Anweisung (§13.10.3) kann verwendet werden, um die Steuerung an den Endpunkt der eingebetteten Anweisung zu übertragen (wodurch eine continue
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 mindestens einmal 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 Anweisung (§13.10.3) kann verwendet werden, um die Steuerung an den Endpunkt der eingebetteten Anweisung zu übertragen (wodurch eine continue
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 Erklärung
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 statement_expression s (§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 die Auswertung ergibt, wird die Steuerung an die eingebettete
true
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 Steuerung an den Endpunkt der for
Anweisung zu übertragen (damit die Iteration der eingebetteten Anweisung endet), und eine Anweisung (§13.10.3) kann verwendet werden, um die Steuerung an den Endpunkt der eingebetteten Anweisung zu übertragen (wodurch die for_iterator ausgeführt und eine continue
weitere Iteration der for
Anweisung ausgeführt wird, beginnend mit dem for_condition).
Die eingebettete Anweisung einer for
Anweisung kann erreicht werden, wenn einer der folgenden Werte zutrifft:
- Die
for
Anweisung ist erreichbar, und es ist keine for_condition vorhanden. - Die
for
Anweisung ist erreichbar, und eine for_condition vorhanden ist und nicht den Konstantenwertfalse
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 eine for_condition vorhanden ist und nicht den Konstantenwerttrue
aufweist.
13.9.5 Die Foreach-Erklärung
Die foreach
Anweisung listet die Elemente einer Auflistung auf und führt eine eingebettete Anweisung für jedes Element der Auflistung aus.
foreach_statement
: '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.
Wenn die foreach_statement beide oder keines ref
enthält und readonly
die Iterationsvariable eine Variable angibt, die schreibgeschützt behandelt wird. Andernfalls bezeichnet die Iterationsvariable, wenn foreach_statement enthält ref
readonly
, eine Variable, die schreibbar 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 angibt, tritt ein Kompilierungszeitfehler auf, wenn die eingebettete Anweisung versucht, sie (über die Zuordnung oder die ++
Operatoren --
) zu ändern oder als Verweis- oder Ausgabeparameter zu übergeben.
Im Folgenden finden Sie PlatzIEnumerable
, , , IEnumerable<T>
IEnumerator
und IEnumerator<T>
verweisen auf die entsprechenden Typen in den Namespaces System.Collections
und System.Collections.Generic
.
Die Kompilierungszeitverarbeitung einer foreach
Anweisung bestimmt zuerst den Auflistungstyp, den Enumerationstyp und den Iterationstyp des Ausdrucks. Diese Bestimmung verläuft 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 Ausdruckstyp
X
vorhanden istdynamic
, 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, lautetdynamic
der Iterationstyp , andernfalls istobject
er . - Ermitteln Sie andernfalls, ob der Typ
X
über eine geeigneteGetEnumerator
Methode verfügt:- Führen Sie die Elementsuche für den Typ
X
mit BezeichnerGetEnumerator
und keine Typargumente durch. Wenn die Elementsuche keine Übereinstimmung erzeugt oder eine Mehrdeutigkeit erzeugt oder eine Übereinstimmung erzeugt, die keine Methodengruppe ist, suchen Sie nach einer aufzählbaren Schnittstelle, wie unten beschrieben. Es wird empfohlen, dass eine Warnung ausgegeben wird, wenn die Mitgliedersuche etwas mit Ausnahme einer Methodengruppe oder ohne Übereinstimmung erzeugt. - 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. - Die Elementsuche wird mit dem Bezeichner
Current
und ohne Typargumente ausgeführtE
. Wenn die Membersuche keine Übereinstimmung erzeugt, ist das Ergebnis ein Fehler, oder das Ergebnis ist alles außer einer öffentlichen Instanzeigenschaft, die das Lesen zulässt, wird ein Fehler erstellt, und es werden keine weiteren Schritte ausgeführt. - Die Elementsuche wird mit dem Bezeichner
MoveNext
und ohne Typargumente ausgeführtE
. Wenn die Elementsuche keine Übereinstimmung erzeugt, ist das Ergebnis ein Fehler, oder das Ergebnis ist alles außer einer Methodengruppe, wird ein Fehler erstellt, und es werden keine weiteren Schritte ausgeführt. - Überladungsauflösung wird für die Methodengruppe mit einer leeren Argumentliste ausgefü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 ist nicht
bool
, 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
Modifizierer enthalten, in diesem Fall ist der zurückgegebene Ausdruck ein variable_reference (§9.5), der optional schreibgeschützt ist.
- Führen Sie die Elementsuche für den Typ
- Suchen Sie andernfalls nach einer aufzählbaren Schnittstelle:
- Wenn unter allen Typen
Tᵢ
, für die eine implizite Konvertierung vonX
zuIEnumerable<Tᵢ>
, gibt es einen eindeutigen TypT
, der nichtdynamic
T
und für alle anderenTᵢ
gibt es eine implizite Konvertierung vonIEnumerable<T>
zuIEnumerable<Tᵢ>
, dann ist der Sammlungstyp die SchnittstelleIEnumerable<T>
, der Enumeratortyp die SchnittstelleIEnumerator<T>
, und der Iterationstyp istT
. - Andernfalls wird, wenn mehr als ein solcher Typ
T
vorhanden ist, ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt. - Andernfalls ist der Sammlungstyp diese Schnittstelle, wenn es eine implizite Konvertierung von
X
derSystem.Collections.IEnumerable
Schnittstelle gibt, der Enumerationstyp die SchnittstelleSystem.Collections.IEnumerator
, und der Iterationstyp 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 E
C
und Iterationstyp T
, ref T
oder ref readonly T
. Eine foreach
Anweisung des Formulars
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 es sich bei der Iterationsvariable um eine Referenzvariable (§9.7) handelt, wird eine foreach
Anweisung des Formulars
foreach (ref V v in x) «embedded_statement»
ist dann gleichbedeutend mit:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
ref V v = ref e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
Die Variable e
ist für den Ausdruck x
oder die eingebettete Anweisung oder einen anderen Quellcode des Programms nicht sichtbar oder zugänglich. Die Referenzvariable v
ist in der eingebetteten Anweisung schreibgeschützt, darf jedoch v
nicht erneut zugewiesen werden (§12.21.3). Wenn keine Identitätskonvertierung (§10.2.2) von (der Iterationstyp) in T
V
(die local_variable_type in der foreach
Anweisung) vorhanden ist, wird ein Fehler erstellt, und es werden keine weiteren Schritte ausgeführt.
Eine foreach
Anweisung des Formulars foreach (ref readonly V v in x) «embedded_statement»
weist ein ähnliches gleichwertiges Formular auf, die Referenzvariable v
befindet ref readonly
sich jedoch in der eingebetteten Anweisung und kann daher nicht erneut zugewiesen oder neu zugewiesen werden.
Hinweis: Wenn
x
der Wertnull
vorhanden ist, wird zur Laufzeit einSystem.NullReferenceException
Fehler ausgelöst. Endnote
Eine Implementierung darf eine bestimmte foreach_statement anders implementieren, z. B. aus Leistungsgründen, solange das Verhalten mit der oben genannten Erweiterung konsistent ist.
Die Platzierung innerhalb der v
while
Schleife ist wichtig für die Erfassung (§12.19.6.2) durch jede anonyme Funktion im embedded_statement.
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 eine eigene Variablev
verfügt, wird die in der ersten Iteration erfasstef
Iteration weiterhin den Wert7
enthalten, was gedruckt wird. (Beachten Sie, dass frühere C#-Versionen außerhalb derwhile
Schleife deklariertv
wurden.)Endbeispiel
Der Textkörper des finally
Blocks wird gemäß den folgenden Schritten konstruiert:
Wenn es eine implizite Konvertierung von
E
derSystem.IDisposable
Schnittstelle gibt, dannWenn
E
es sich um einen nicht nullablen Werttyp handelt, wird diefinally
Klausel auf das semantische Äquivalent von:finally { ((System.IDisposable)e).Dispose(); }
Andernfalls wird die
finally
Klausel auf das semantische Äquivalent von:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }
außer wenn
E
es sich um einen Werttyp oder einen Typparameter handelt, der auf einen Werttyp instanziiert wird, tritt die Konvertierung vone
"inSystem.IDisposable
" nicht auf.
Andernfalls wird die Klausel auf einen leeren Block erweitert, wenn
E
esfinally
sich um einen versiegelten Typ handelt: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 Elemente durchlaufen, sodass die Indizes der äußerst rechten Dimension zuerst, dann die nächste linke Dimension usw. nach links erhöht werden.
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 erzeugte Ausgabe lautet wie folgt:
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 abgeleitet, umint
den Iterationstyp vonnumbers
.Endbeispiel
13.10 Jump-Anweisungen
13.10.1 Allgemein
Sprunganweisungen übertragen bedingungslos 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 Jump-Anweisung innerhalb eines Blocks auftritt und das Ziel dieser Sprung-Anweisung außerhalb dieses Blocks liegt, wird die Jump-Anweisung zum Beenden des Blocks gesagt. 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 ist durch das Vorhandensein von dazwischen liegenden try
Anweisungen kompliziert. Ohne solche try
Aussagen überträgt eine Sprung-Anweisung bedingungslos die Kontrolle von der Sprung-Anweisung an ihr Ziel. In Anwesenheit solcher dazwischenliegenden try
Anweisungen ist die Ausführung komplexer. Wenn die Jump-Anweisung einen oder try
mehrere Blöcke mit zugeordneten finally
Blöcken verlässt, wird das Steuerelement zunächst in den finally
Block der innersten try
Anweisung übertragen. Wenn und wenn das Steuerelement den Endpunkt eines finally
Blocks erreicht, wird das Steuerelement in den finally
Block der nächsten eingeschlossenen 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"); } }
die
finally
zweitry
Anweisungen zugeordneten Blöcke werden ausgeführt, bevor das Steuerelement auf das Ziel der Sprunganweisungen übertragen wird. Die erzeugte Ausgabe lautet wie folgt:Before break Innermost finally block Outermost finally block After break
Endbeispiel
13.10.2 Die Break-Anweisung
Die break
Anweisung beendet die nächste eingeschlossene switch
, , while
, do
, , for
oder foreach
Anweisung.
break_statement
: 'break' ';'
;
Das Ziel einer break
Anweisung ist der Endpunkt der nächstgelegenen eingeschlossenen switch
, , while
, do
, , for
oder foreach
Anweisung. Wenn eine break
Anweisung nicht von einem switch
, while
, , do
, , for
oder foreach
einer 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 Kontrolle über mehrere Schachtelungsebenen hinweg zu übertragen, muss eine goto
Anweisung (§13.10.4) verwendet werden.
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 odertry
mehrere Blöcke mit zugeordnetenfinally
Blöcken verlässt, wird das Steuerelement zunächst in denfinally
Block der innerstentry
Anweisung übertragen. Wenn und wenn das Steuerelement den Endpunkt einesfinally
Blocks erreicht, wird das Steuerelement in denfinally
Block der nächsten eingeschlossenentry
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 Anweisung "Continue"
Die continue
Anweisung beginnt eine neue Iteration der nächstgelegenen eingeschlossenen while
, , do
, , for
oder foreach
Anweisung.
continue_statement
: 'continue' ';'
;
Das Ziel einer continue
Anweisung ist der Endpunkt der eingebetteten Anweisung der nächstgelegenen eingeschlossenen while
, , do
, for
oder foreach
Anweisung. Wenn eine continue
Anweisung nicht durch ein while
, do
, , for
oder foreach
eine 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 Kontrolle über mehrere Schachtelungsebenen hinweg zu übertragen, muss eine goto
Anweisung (§13.10.4) verwendet werden.
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 odertry
mehrere Blöcke mit zugeordnetenfinally
Blöcken verlässt, wird das Steuerelement zunächst in denfinally
Block der innerstentry
Anweisung übertragen. Wenn und wenn das Steuerelement den Endpunkt einesfinally
Blocks erreicht, wird das Steuerelement in denfinally
Block der nächsten eingeschlossenentry
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
Bezeichner-Anweisung ist die beschriftete Anweisung mit der angegebenen Bezeichnung. 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 Steuerung aus einem geschachtelten Bereich zu übertragen.Endnote
Das Ziel einer goto case
Anweisung ist die Anweisungsliste in der unmittelbar eingeschlossenen switch
Anweisung (§13.8.3), die eine case
Beschriftung mit einem konstanten Muster des angegebenen Konstantenwerts und ohne Schutz enthält. Wenn die Anweisung nicht von einer switch
Anweisung eingeschlossen wird, wenn die goto case
nächste eingeschlossene switch
Anweisung keine solche case
anweisung enthält oder die constant_expression nicht implizit (§10.2) in den Regeltyp der nächstgelegenen eingeschlossenen switch
Anweisung konvertiert wird, tritt ein Kompilierungszeitfehler 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 Bezeichnung enthält default
, 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 odertry
mehrere Blöcke mit zugeordnetenfinally
Blöcken verlässt, wird das Steuerelement zunächst in denfinally
Block der innerstentry
Anweisung übertragen. Wenn und wenn das Steuerelement den Endpunkt einesfinally
Blocks erreicht, wird das Steuerelement in denfinally
Block der nächsten eingeschlossenentry
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 Rückgabe-Anweisung
Die return
Anweisung gibt die Steuerung an den aktuellen Aufrufer des Funktionselements zurück, in dem die Rückgabeanweisung angezeigt wird, optional einen Wert oder eine variable_reference (§9.5).
return_statement
: 'return' ';'
| 'return' expression ';'
| 'return' 'ref' variable_reference ';'
;
Ein return_statement ohne Ausdruck wird als Rückgabe-no-Wert bezeichnet. Ein Ausdruck, der einen Ausdruck enthältref
, wird als Rückgabe-nach-Bezug bezeichnet, und ein Ausdruck, der nur einen Ausdruck enthält, wird als Rückgabe-nach-Wert bezeichnet.
Es handelt sich um einen Kompilierungsfehler, um einen Rückgabe-No-Wert aus einer Methode zu verwenden, die als Rückgabe-nach-Wert oder Rückgabe-nach-Bezug (§15.6.1) deklariert wurde.
Es handelt sich um einen Kompilierungsfehler, um einen Rückgabe-nach-Bezug einer Methode zu verwenden, die als Rückgabe-no-Wert oder Rückgabe-nach-Wert deklariert wurde.
Es handelt sich um einen Kompilierungsfehler, um einen Rückgabe-nach-Wert aus einer Methode zu verwenden, die als Rückgabe-no-Wert oder Rückgabe-nach-Bezug deklariert wurde.
Es handelt sich um einen Kompilierzeitfehler, wenn ein Ausdruck kein variable_reference ist oder ein Verweis auf eine Variable ist, deren ref-safe-context kein Aufruferkontext ist (§9.7.2).
Es handelt sich um einen Kompilierungsfehler, um einen Rückgabe-by-Ref aus einer Methode zu verwenden, die mit der method_modifier async
deklariert ist.
Ein Funktionsmemmemm wird angegeben, einen Wert zu berechnen, wenn es sich um eine Methode mit einer Rückgabe-nach-Wert-Methode (§15.6.11), einem Rückgabe-nach-Wert-Accessor einer Eigenschaft oder eines indexers oder eines benutzerdefinierten Operators handelt. Funktionsmember, die rückgabefreie Werte sind, berechnen keinen Wert und sind Methoden mit dem effektiven Rückgabetyp void
, Festlegen von Accessoren von Eigenschaften und Indexern, Hinzufügen und Entfernen von Accessoren von Ereignissen, Instanzkonstruktoren, statischen Konstruktoren und Finalizern. Funktionsmember, die rückgabeweise zurückgegeben werden, berechnen keinen Wert.
Für einen Rückgabe-nach-Wert muss eine implizite Konvertierung (§10.2) vom Typ des Ausdrucks in den effektiven Rückgabetyp (§15.6.11) des enthaltenden Funktionselements vorhanden sein. Für einen Rückgabe-by-Ref muss eine Identitätskonvertierung (§10.2.2) zwischen dem Ausdruckstyp und dem effektiven Rückgabetyp des enthaltenden Funktionsmemems vorhanden sein.
return
Anweisungen können auch im Textkörper anonymer Funktionsausdrücke (§12.19) verwendet werden und daran teilnehmen, zu bestimmen, welche Konvertierungen für diese Funktionen vorhanden sind (§10.7.1).
Es handelt sich um einen Kompilierungszeitfehler für eine return
Anweisung, die in einem finally
Block (§13.11) angezeigt wird.
Eine return
Anweisung wird wie folgt ausgeführt:
- Bei einem Rückgabe-nach-Wert wird der Ausdruck ausgewertet und sein Wert wird durch eine implizite Konvertierung in den effektiven Rückgabetyp der enthaltenden Funktion konvertiert. Das Ergebnis der Konvertierung wird zum Ergebniswert, der von der Funktion erzeugt wird. Bei einem Rückgabe-by-Ref wird der Ausdruck ausgewertet, und das Ergebnis muss als Variable klassifiziert werden. Wenn die rückgabe-by-ref der eingeschlossenen Methode enthalten
readonly
ist, ist die resultierende Variable schreibgeschützt. - Wenn die
return
Anweisung von einem odertry
catch
mehreren Blöcken mit zugeordnetenfinally
Blöcken eingeschlossen wird, wird das Steuerelement zunächst in denfinally
Block der innerstentry
Anweisung übertragen. Wenn und wenn das Steuerelement den Endpunkt einesfinally
Blocks erreicht, wird das Steuerelement in denfinally
Block der nächsten eingeschlossenentry
Anweisung übertragen. Dieser Vorgang wird wiederholt, bis diefinally
Blöcke aller eingeschlossenentry
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 wie in (§15.15.3) beschrieben aufgezeichnet.
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 throw
Anweisung 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 muss implizit in System.Exception
konvertiert werden, und das Ergebnis der Auswertung des Ausdrucks wird vor dem Auslösen in den Ausdruck konvertiert System.Exception
. Wenn das Ergebnis der Konvertierung lautet null
, wird stattdessen ein System.NullReferenceException
Fehler ausgelöst.
Eine throw
Anweisung ohne Ausdruck kann nur in einem catch
Block verwendet werden, in diesem Fall löst diese Anweisung die Ausnahme erneut aus, die derzeit von diesem catch
Block behandelt wird.
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 das Steuerelement in eine eingeschlossene try
Anweisung in die erste catch
Klausel übertragen, die die Ausnahme behandeln kann. Der Prozess, der vom Punkt der Ausnahme ausgelöst wird, an den Punkt der Übertragung der Steuerung an einen geeigneten Ausnahmehandler wird als Ausnahmeverteilung 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 Funktionselement wird jede
try
Anweisung, die den Wurfpunkt einschließt, untersucht. 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 desS
Auslösenpunkts einschließt und eineS
odercatch
mehrere Klauseln aufweist, werden diecatch
Klauseln in der Reihenfolge der Darstellung untersucht, um einen geeigneten Handler für die Ausnahme zu finden. Die erstecatch
Klausel, die einen AusnahmetypT
angibt (oder ein Typparameter, der zur Laufzeit einen AusnahmetypT
angibt), sodass der Laufzeittyp derE
abgeleiteten ElementeT
als Übereinstimmung betrachtet wird. 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, wird diesecatch
Klausel als Übereinstimmung betrachtet, wenn der Ausnahmefilter ausgewertet wirdtrue
. Eine allgemeinecatch
(§13.11)-Klausel gilt als Übereinstimmung für jeden Ausnahmetyp. Wenn sich eine Abgleichsklauselcatch
befindet, wird die Ausnahmeverteilung abgeschlossen, indem die Steuerung an den Block diesercatch
Klausel übertragen wird. - Andernfalls wird das Steuerelement an den
finally
Block übertragen, wenn dertry
Block oder eincatch
Block desS
Wurfpunkts eingeschlossen ist undS
einfinally
Block vorhanden ist. 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.15.3 beschrieben.
Wenn die aktuelle Funktion asynchron und
void
-returning ist, wird der Synchronisierungskontext des aktuellen Threads wie in §15.15.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 dieser Beendigung werden durch implementierungsdefiniert.
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 der Typ oder seine effektive Basisklasse, wenn es sich um eine type_parameter handelt, muss oder ein Typ seinSystem.Exception
, 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 der exception_filter und catch
des Blocks stellt die Ausnahmevariable die derzeit behandelte Ausnahme dar. 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 objekt abgeleitetes
System.Exception
Objekt dargestellt werden können, obwohl solche Ausnahmen niemals durch C#-Code generiert werden konnten. 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. Endnote
Um einen Handler für eine Ausnahme zu finden, catch
werden 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. Endnote
Innerhalb eines catch
Blocks kann eine throw
Anweisung (§13.10.6) ohne Ausdruck verwendet werden, um die Ausnahme, die catch
vom Block abgefangen wurde, erneut auszuwerfen. 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 erzeugte Ausgabe wie folgt lautet:Exception in F: G Exception in Main: G
Wenn der erste
catch
Block ausgelöste
wurde, anstatt die aktuelle Ausnahme erneut zu drosseln, würde die erzeugte Ausgabe wie folgt aussehen:Exception in F: G Exception in Main: F
Endbeispiel
Es handelt sich um einen Kompilierungszeitfehler für ein , continue
oder goto
eine break
Anweisung, um die Kontrolle aus einem finally
Block zu übertragen. Wenn ein break
, continue
oder goto
eine Anweisung in einem finally
Block auftritt, muss sich das Ziel der Anweisung innerhalb desselben finally
Blocks befinden, oder andernfalls tritt ein Kompilierungszeitfehler 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 ist eine Übereinstimmung, wenn der Ausnahmetyp mit einem beliebigen exception_specifier übereinstimmt und alle exception_filter wahr ist. Eine catch
Klausel ohne exception_specifier entspricht einem 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 eingeschlossene 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 eingeschlossene try
Anweisung weitergegeben.
Die Anweisungen eines finally
-Blocks werden immer ausgeführt, wenn die Steuerung eine try
-Anweisung verlässt. Dies gilt, ob die Steuerungsübertragung als Ergebnis einer normalen Ausführung auftritt, als Ergebnis der Ausführung eines break
, continue
, goto
oder return
einer Anweisung oder als Ergebnis der Weitergabe einer Ausnahme aus der try
Anweisung. 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 beim Verteilen erfolgte, 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
KlauselMethod
vor der Übertragung des Steuerelements an die eingeschlossene Abgleichsklauselcatch
ausgeführt. Die resultierende Ausgabe lautet: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 aktivierten und deaktivierten Anweisungen
Die checked
Anweisungen unchecked
werden verwendet, um den Überlaufüberprüfungskontext für arithmetische Operationen und Konvertierungen des integralen Typs 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
Anweisungen unchecked
entsprechen genau den checked
Operatoren und unchecked
Operatoren (§12.8.20), mit der Ausnahme, dass sie anstelle von Ausdrücken auf Blöcken arbeiten.
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
Erklärung gibt einen Wert eines Typs an, der als Bezug bekannt ist. Für den Ausdruck einer Anweisung wird jemals eine implizite Boxkonvertierung (§10.2.9) ausgeführt. Daher ist es ein Kompilierungszeitfehler für den Ausdruck, um einen Wert eines value_type zu kennzeichnen.lock
Eine lock
Anweisung des Formulars
lock (x)
…
ist x
ein Ausdruck einer reference_type genau gleichbedeutend mit:
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 für gegenseitigen Ausschluss gehalten wird, kann code, der im selben Ausführungsthread ausgeführt wird, auch die Sperre 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 implementiert, die eine einzelne parameterlose Methode mit dem Namen Dispose
enthält. Code, der eine Ressource verwendet, kann aufgerufen Dispose
werden, um anzugeben, dass die Ressource nicht mehr benötigt wird.
Wenn die Form der resource_acquisition local_variable_declaration ist, muss der Typ des local_variable_declaration entweder dynamic
oder ein Typ sein, der implizit in konvertiert System.IDisposable
werden kann. Wenn die Form von resource_acquisition Ausdruck ist, muss dieser Ausdruck implizit in System.IDisposable
.
Lokale Variablen, die in einem 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.
Eine using
Anweisung wird in drei Teile ü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 Dispose
ausgeführt, und es wird keine Ausnahme ausgelöst. Wenn die Ressource vom Typ dynamic
ist, wird sie dynamisch über eine implizite dynamische Konvertierung (§10.2.10) IDisposable
während des Erwerbs konvertiert, um sicherzustellen, dass die Konvertierung vor der Verwendung und Entsorgung erfolgreich ist.
Eine using
Anweisung des Formulars
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 Gießung nicht resource
System.IDisposable
dazu führen darf, dass Boxen auftreten.
ResourceType
Andernfalls ist dynamic
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 kann nicht auf die eingebettete Anweisung zugegriffen werden.
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 des Formulars:
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
selbst als verwendet 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 des Formulars
using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) «statement»
entspricht genau einer Abfolge geschachtelter 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
TextWriter
Schnittstelle undTextReader
die Klassen dieIDisposable
Schnittstelle implementieren, kann das Beispiel Anweisungen verwendenusing
, um sicherzustellen, dass die zugrunde liegende Datei nach den Schreib- oder Lesevorgängen ordnungsgemäß geschlossen wird.Endbeispiel
13.15 Die Renditeerklärung
Die yield
Anweisung wird in einem Iteratorblock (§13.3) verwendet, um dem Enumerationsobjekt (§15.14.5) oder dem aufzählbaren Objekt (§15.14.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 eine besondere Bedeutung nur, wenn sie unmittelbar vor einem Schlüsselwort oder break
Schlüsselwort return
verwendet wird.
Es gibt mehrere Einschränkungen, an denen eine yield
Anweisung angezeigt werden kann, wie in der folgenden Abbildung beschrieben.
- Es handelt sich um einen Kompilierzeitfehler für eine
yield
Anweisung (eines der beiden Formulare), die außerhalb eines method_body, operator_body oder accessor_body angezeigt werden soll. - Es handelt sich um einen Kompilierungszeitfehler für eine
yield
Anweisung (aus beiden Formularen), die innerhalb einer anonymen Funktion angezeigt wird. - Es handelt sich um einen Kompilierungsfehler für eine Anweisung (in beiden
yield
Formularen), die in derfinally
Klausel einertry
Anweisung angezeigt wird. - Es handelt sich um einen Kompilierzeitfehler, damit eine
yield return
Anweisung an einer beliebigen Stelle in einertry
Anweisung angezeigt wird, die alle 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.14.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 Iteratorblocks wird angehalten. Wenn sich die
yield return
Anweisung in einem odertry
mehreren Blöcken befindet, werden die zugehörigenfinally
Blöcke zurzeit nicht ausgeführt. - Die
MoveNext
Methode des Enumerator-Objekts wird an den Aufrufer zurückgegebentrue
, der angibt, dass das Enumerationsobjekt erfolgreich zum nächsten Element erweitert 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 wenn das Steuerelement den Endpunkt einesfinally
Blocks erreicht, wird das Steuerelement in denfinally
Block der nächsten eingeschlossenentry
Anweisung übertragen. Dieser Vorgang wird wiederholt, bis diefinally
Blöcke aller eingeschlossenentry
Anweisungen ausgeführt wurden. - Das Steuerelement 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