Freigeben über


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 die if Verzweigung erfordert. Wenn dieser Code zulässig wäre, würde die Variable i deklariert, aber sie konnte nie verwendet werden. Beachten Sie jedoch, dass das Beispiel gültig ist, indem die Deklaration in einem Block platziert iwird.

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 Wert falseerzeugt, der Console.WriteLine Aufruf wird als nicht erreichbar betrachtet. Wenn i es sich jedoch um eine lokale Variable handelt

void 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 der F 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 ersten Console.WriteLine Ausdrucksanweisung erreichbar ist (§13.7 und §13.3).
  • Die zweite Console.WriteLine Ausdrucksanweisung ist erreichbar, da der boolesche Ausdruck der if Anweisung nicht den Konstantenwert falseaufweist.

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 eine break 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 (aber yield 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 Tder 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 Tder 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 asyncdeklariert 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 thiswerden 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 Anfangspunkt L nicht erreichbar ist. Da der Anfangspunkt L nicht erreichbar ist, ist die Anweisung nach dem Endpunkt L 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 noch base auf this lokale Variablen, Parameter oder nicht statische lokale Funktionen aus dem eingeschlossenen Bereich. Alle diese sind jedoch in einem nameof() 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 und x == 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 Formulars

if (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 der if Anweisung übertragen.
  • Wenn der boolesche Ausdruck ergibt false und ein else 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 der if Anweisung übertragen.
  • Wenn der boolesche Ausdruck ergibt false und ein else Teil nicht vorhanden ist, wird das Steuerelement an den Endpunkt der if Anweisung übertragen.

Die erste eingebettete Anweisung einer if Anweisung ist erreichbar, wenn die if Anweisung erreichbar ist und der boolesche Ausdruck nicht den Konstantenwert falseaufweist.

Die zweite eingebettete Anweisung einer if Anweisung, falls vorhanden, ist erreichbar, wenn die if Anweisung erreichbar ist und der boolesche Ausdruck nicht den Konstantenwert trueaufweist.

Der Endpunkt einer if Anweisung ist erreichbar, wenn der Endpunkt mindestens einer der eingebetteten Anweisungen erreichbar ist. Darüber hinaus ist der Endpunkt einer if Anweisung ohne else Teil erreichbar, wenn die if Anweisung erreichbar ist und der boolesche Ausdruck nicht den Konstantenwert trueaufweist.

13.8.3 Die Switch-Anweisung

Die switch Anweisung wählt für die Ausführung einer Anweisungsliste mit einer zugeordneten 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, charulonglongstringuintbooloder ein enum_type, oder wenn es sich um den Nullwerttyp handelt, der einem dieser Typen entspricht, dann ist dies der Steuerungstyp der switch Anweisung. byte
  • Andernfalls ist genau eine benutzerdefinierte implizite Konvertierung vom Typ des Switchausdrucks in einen der folgenden möglichen Regeltypen vorhanden: sbyte, , byte, short, ushortulongintlongcharuintoderstring, 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 derselben switch 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 übereinstimmenden case Bezeichnung in die Anweisungsliste übertragen wird.
    • Andernfalls wird das Steuerelement, wenn eine default Bezeichnung vorhanden ist, in die Anweisungsliste nach der default Bezeichnung übertragen.
    • Andernfalls wird die Steuerung an den Endpunkt der switch Anweisung übertragen.

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 truewird. 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 oder goto 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 der switch 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 caseoder goto default einer Anweisung, aber jedes Konstrukt, das den Endpunkt der Anweisungsliste nicht erreichbar rendert, ist zulässig. Beispielsweise ist eine while Anweisung, die vom booleschen Ausdruck true gesteuert wird, bekannt, dass sie nie ihren Endpunkt erreicht. Ebenso überträgt eine throw oder return 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 Typ stringsein. 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 einer case Bezeichnungskonstante übereinstimmt. end note When the governing type of a switch statement is string or a nullable value type, the value null is permitted as a case 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.
  • 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.
  • Auf die Switchbezeichnung wird durch eine erreichbare goto case oder goto 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 erreichbare break Anweisung, die die switch 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 nullder 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 einer continue Anweisung), wird das Steuerelement an den Anfang der while Anweisung übertragen.
  • Wenn der boolesche Ausdruck ergibt false, wird das Steuerelement an den Endpunkt der while 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 falseaufweist.

Der Endpunkt einer while Anweisung ist erreichbar, wenn mindestens einer der folgenden Werte zutrifft:

  • Die while Anweisung enthält eine erreichbare break Anweisung, die die while Anweisung verlässt.
  • Die while Anweisung ist erreichbar, und der boolesche Ausdruck hat nicht den Konstantenwert true.

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 ergibt true, wird das Steuerelement an den Anfang der do Anweisung übertragen. Andernfalls wird die Steuerung an den Endpunkt der do 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 erreichbare break Anweisung, die die do 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 trueAnweisung übertragen. Wenn und wenn das Steuerelement den Endpunkt der eingebetteten Anweisung (möglicherweise von der Ausführung einer continue 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 der for 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 Konstantenwert falseaufweist.

Der Endpunkt einer for Anweisung ist erreichbar, wenn mindestens einer der folgenden Werte zutrifft:

  • Die for Anweisung enthält eine erreichbare break Anweisung, die die for Anweisung verlässt.
  • Die for Anweisung ist erreichbar, und eine for_condition vorhanden ist und nicht den Konstantenwert trueaufweist.

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 readonlydie 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> IEnumeratorund 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 die IEnumerable Schnittstelle (da System.Array diese Schnittstelle implementiert wird). Der Sammlungstyp ist die IEnumerable Schnittstelle, der Enumerationstyp ist die IEnumerator Schnittstelle und der Iterationstyp ist der Elementtyp des Arraytyps X.
  • Wenn der Ausdruckstyp X vorhanden istdynamic, gibt es eine implizite Konvertierung von Ausdruck in die IEnumerable Schnittstelle (§10.2.10). Der Sammlungstyp ist die IEnumerable Schnittstelle, und der Enumerationstyp ist die IEnumerator Schnittstelle. Wenn der var Bezeichner als local_variable_type angegeben wird, lautet dynamicder Iterationstyp , andernfalls ist objecter .
  • Ermitteln Sie andernfalls, ob der Typ X über eine geeignete GetEnumerator Methode verfügt:
    • Führen Sie die Elementsuche für den Typ X mit Bezeichner GetEnumerator 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 der GetEnumerator 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 ist E, und der Iterationstyp ist der Typ der Current Eigenschaft. Die Current Eigenschaft kann den ref Modifizierer enthalten, in diesem Fall ist der zurückgegebene Ausdruck ein variable_reference (§9.5), der optional schreibgeschützt ist.
  • Suchen Sie andernfalls nach einer aufzählbaren Schnittstelle:
    • Wenn unter allen TypenTᵢ, für die eine implizite Konvertierung von X zu IEnumerable<Tᵢ>, gibt es einen eindeutigen TypT, der nicht dynamic T und für alle anderen Tᵢ gibt es eine implizite Konvertierung von IEnumerable<T> zu IEnumerable<Tᵢ>, dann ist der Sammlungstyp die Schnittstelle IEnumerable<T>, der Enumeratortyp die Schnittstelle IEnumerator<T>, und der Iterationstyp ist T.
    • Andernfalls wird, wenn mehr als ein solcher Typ Tvorhanden 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 der System.Collections.IEnumerable Schnittstelle gibt, der Enumerationstyp die Schnittstelle System.Collections.IEnumerator, und der Iterationstyp ist object.
    • Andernfalls wird ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt.

Die obigen Schritte erzeugen, falls erfolgreich, eindeutig einen Sammlungstyp, Enumeratortyp E Cund Iterationstyp T, ref Toder 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 Wert nullvorhanden ist, wird zur Laufzeit ein System.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 der while Schleife deklariert würde, würde sie für alle Iterationen freigegeben, und ihr Wert nach der for Schleife wäre der endwert, 13was der Aufruf der f Schleife drucken würde. Da jede Iteration über eine eigene Variable vverfügt, wird die in der ersten Iteration erfasste f Iteration weiterhin den Wert 7enthalten, was gedruckt wird. (Beachten Sie, dass frühere C#-Versionen außerhalb der while Schleife deklariert v wurden.)

Endbeispiel

Der Textkörper des finally Blocks wird gemäß den folgenden Schritten konstruiert:

  • Wenn es eine implizite Konvertierung von E der System.IDisposable Schnittstelle gibt, dann

    • Wenn E es sich um einen nicht nullablen Werttyp handelt, wird die finally 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 von e "in System.IDisposable " nicht auf.

  • Andernfalls wird die Klausel auf einen leeren Block erweitert, wenn E es finally 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, um intden Iterationstyp von numbers.

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 zwei try 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, , foroder foreach Anweisung.

break_statement
    : 'break' ';'
    ;

Das Ziel einer break Anweisung ist der Endpunkt der nächstgelegenen eingeschlossenen switch, , while, do, , foroder foreach Anweisung. Wenn eine break Anweisung nicht von einem switch, while, , do, , foroder foreach einer Anweisung eingeschlossen ist, tritt ein Kompilierungszeitfehler auf.

Wenn mehrere switch, , while, do, foroder foreach Anweisungen miteinander verschachtelt sind, gilt eine break Anweisung nur für die innerste Anweisung. Um die 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 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.
  • 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, , foroder foreach Anweisung.

continue_statement
    : 'continue' ';'
    ;

Das Ziel einer continue Anweisung ist der Endpunkt der eingebetteten Anweisung der nächstgelegenen eingeschlossenen while, , do, foroder foreach Anweisung. Wenn eine continue Anweisung nicht durch ein while, do, , foroder foreach eine Anweisung eingeschlossen ist, tritt ein Kompilierungszeitfehler auf.

Wenn mehrere while, , do, foroder foreach Anweisungen miteinander verschachtelt sind, gilt eine continue Anweisung nur für die innerste Anweisung. Um die 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 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.
  • 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 Beispiel

class 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 caseanweisung 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 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.
  • 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 asyncdeklariert 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 readonlyist, ist die resultierende Variable schreibgeschützt.
  • Wenn die return Anweisung von einem oder try catch mehreren Blöcken mit zugeordneten finally Blöcken eingeschlossen wird, 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 eingeschlossenen try 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.Exceptionkonvertiert 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 Anweisung S, beginnend mit der innersten try Anweisung und endet mit der äußersten try Anweisung, werden die folgenden Schritte ausgewertet:

    • Wenn der try Block des S Auslösenpunkts einschließt und eine S oder catch mehrere Klauseln aufweist, werden die catch Klauseln in der Reihenfolge der Darstellung untersucht, um einen geeigneten Handler für die Ausnahme zu finden. Die erste catch Klausel, die einen Ausnahmetyp T angibt (oder ein Typparameter, der zur Laufzeit einen Ausnahmetyp Tangibt), sodass der Laufzeittyp der E abgeleiteten Elemente T als Übereinstimmung betrachtet wird. Wenn die Klausel einen Ausnahmefilter enthält, wird das Ausnahmeobjekt der Ausnahmevariablen zugewiesen, und der Ausnahmefilter wird ausgewertet. Wenn eine catch Klausel einen Ausnahmefilter enthält, wird diese catch Klausel als Übereinstimmung betrachtet, wenn der Ausnahmefilter ausgewertet wird true. Eine allgemeine catch (§13.11)-Klausel gilt als Übereinstimmung für jeden Ausnahmetyp. Wenn sich eine Abgleichsklausel catch befindet, wird die Ausnahmeverteilung abgeschlossen, indem die Steuerung an den Block dieser catch Klausel übertragen wird.
    • Andernfalls wird das Steuerelement an den finally Block übertragen, wenn der try Block oder ein catch Block des S Wurfpunkts eingeschlossen ist und S ein finally Block vorhanden ist. Wenn der finally-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 des finally Blocks erreicht.
  • 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.ExceptionObjekt dargestellt werden können, obwohl solche Ausnahmen niemals durch C#-Code generiert werden konnten. Eine allgemeine catch Klausel kann verwendet werden, um solche Ausnahmen abzufangen. Daher unterscheidet sich eine allgemeine catch Klausel semantisch von einem, der den Typ System.Exceptionangibt, 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öst e 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 , continueoder goto eine breakAnweisung, um die Kontrolle aus einem finally Block zu übertragen. Wenn ein break, continueoder 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, gotooder 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 eingeschlossenen catch Klauseln zu untersuchen und alle Ausnahmefilter auszuführen. Anschließend wird die finally Klausel Method vor der Übertragung des Steuerelements an die eingeschlossene Abgleichsklausel catch 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 eines catch Blocks ist erreichbar.
  • Wenn ein finally Block vorhanden ist, ist der Endpunkt des finally 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 Disposeenthä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.IDisposablewerden 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 dynamicdie 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 und TextReader die Klassen die IDisposable Schnittstelle implementieren, kann das Beispiel Anweisungen verwenden using , 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' ';'
    ;

yieldist 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 der finally Klausel einer try Anweisung angezeigt wird.
  • Es handelt sich um einen Kompilierzeitfehler, damit eine yield return Anweisung an einer beliebigen Stelle in einer try 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 oder try mehreren Blöcken befindet, werden die zugehörigen finally Blöcke zurzeit nicht ausgeführt.
  • Die MoveNext Methode des Enumerator-Objekts wird an den Aufrufer zurückgegeben true , 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 oder try mehreren Blöcken mit zugeordneten finally Blöcken eingeschlossen wird, 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 eingeschlossenen try Anweisungen ausgeführt wurden.
  • Das Steuerelement wird an den Aufrufer des Iteratorblocks zurückgegeben. Dies ist entweder die MoveNext Methode oder Dispose 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.