Udostępnij za pośrednictwem


13 instrukcji

13.1 Ogólne

Język C# zawiera różne instrukcje.

Uwaga: Większość tych instrukcji będzie znana deweloperom, którzy programowali w języku C i C++. notatka końcowa

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) i fixed_statement (§23.7) są dostępne tylko w niebezpiecznym kodzie (§23).

Embedded_statement nonterminal jest używany do instrukcji, które pojawiają się w innych instrukcjach. Użycie embedded_statement, a nie instrukcji wyklucza użycie instrukcji deklaracji i instrukcji oznaczonych etykietami w tych kontekstach.

Przykład: kod

void F(bool b)
{
   if (b)
      int i = 44;
}

powoduje błąd czasu kompilacji, ponieważ if instrukcja wymaga embedded_statement, a nie instrukcji dla jej if gałęzi. Gdyby ten kod był dozwolony, zmienna i zostanie zadeklarowana, ale nigdy nie można jej użyć. Należy jednak pamiętać, że umieszczenie ideklaracji "w bloku" jest prawidłowe.

przykład końcowy

13.2 Punkty końcowe i osiągalność

Każda instrukcja ma punkt końcowy. Intuicyjnie mówiąc, punkt końcowy instrukcji to lokalizacja, która jest natychmiast zgodna z instrukcją . Reguły wykonywania instrukcji złożonych (instrukcje zawierające instrukcje osadzone) określają akcję wykonywaną, gdy kontrolka osiągnie punkt końcowy osadzonej instrukcji.

Przykład: gdy kontrolka osiągnie punkt końcowy instrukcji w bloku, kontrolka zostanie przeniesiona do następnej instrukcji w bloku. przykład końcowy

Jeśli oświadczenie może zostać osiągnięte przez wykonanie, mówi się, że oświadczenie jest osiągalne. Z drugiej strony, jeśli nie ma możliwości wykonania oświadczenia, oświadczenie mówi się, że nie jest osiągalne.

Przykład: w poniższym kodzie

void F()
{
    Console.WriteLine("reachable");
    goto Label;
    Console.WriteLine("unreachable");
  Label:
    Console.WriteLine("reachable");
}

drugie wywołanie elementu Console.WriteLine nie jest osiągalne, ponieważ nie ma możliwości wykonania instrukcji.

przykład końcowy

Ostrzeżenie jest zgłaszane, jeśli instrukcja inna niż throw_statement, blok lub empty_statement jest niedostępna. Nie jest to w szczególności błąd instrukcji, która nie jest osiągalna.

Uwaga: Aby określić, czy określona instrukcja lub punkt końcowy jest osiągalna, kompilator wykonuje analizę przepływu zgodnie z regułami dostępności zdefiniowanymi dla każdej instrukcji. Analiza przepływu uwzględnia wartości wyrażeń stałych (§12.23), które kontrolują zachowanie instrukcji, ale nie są brane pod uwagę możliwe wartości wyrażeń niestałych. Innymi słowy, na potrzeby analizy przepływu sterowania wyrażenie niestałych danego typu jest uznawane za dowolne możliwe wartości tego typu.

W przykładzie

void F()
{
    const int i = 1;
    if (i == 2)
        Console.WriteLine("unreachable");
}

wyrażenie logiczne instrukcji if jest wyrażeniem stałym, ponieważ oba operandy == operatora są stałymi. Ponieważ wyrażenie stałe jest obliczane w czasie kompilacji, generowanie wartości falsepowoduje Console.WriteLine , że wywołanie jest uznawane za niedostępne. Jeśli i jednak zostanie zmieniona na zmienną lokalną

void F()
{
    int i = 1;
    if (i == 2)
        Console.WriteLine("reachable");
}

Console.WriteLine wywołanie jest uważane za osiągalne, mimo że w rzeczywistości nigdy nie zostanie wykonane.

notatka końcowa

Blok elementu członkowskiego funkcji lub funkcji anonimowej jest zawsze uznawany za osiągalny. Oceniając kolejno reguły osiągalności każdej instrukcji w bloku, można określić osiągalność dowolnej danej instrukcji.

Przykład: w poniższym kodzie

void F(int x)
{
    Console.WriteLine("start");
    if (x < 0)
        Console.WriteLine("negative");
}

osiągalność sekundy Console.WriteLine jest określana w następujący sposób:

  • Pierwsza Console.WriteLine instrukcja wyrażenia jest osiągalna, ponieważ blok F metody jest osiągalny (§13.3).
  • Punkt końcowy pierwszej Console.WriteLine instrukcji wyrażenia jest osiągalny, ponieważ ta instrukcja jest osiągalna (§13.7 i §13.3).
  • Instrukcja if jest osiągalna, ponieważ punkt końcowy pierwszej Console.WriteLine instrukcji wyrażenia jest osiągalny (§13.7 i §13.3).
  • Druga Console.WriteLine instrukcja wyrażenia jest osiągalna, ponieważ wyrażenie logiczne instrukcji if nie ma stałej wartości false.

przykład końcowy

Istnieją dwie sytuacje, w których jest to błąd czasu kompilacji, aby punkt końcowy instrukcji był osiągalny:

  • switch Ponieważ instrukcja nie zezwala na przejście sekcji przełącznika do następnej sekcji przełącznika, jest to błąd czasu kompilacji dla punktu końcowego listy instrukcji sekcji przełącznika, która ma być osiągalna. Jeśli wystąpi ten błąd, zazwyczaj oznacza to, że break brakuje instrukcji.

  • Jest to błąd czasu kompilacji dla punktu końcowego bloku elementu członkowskiego funkcji lub funkcji anonimowej, która oblicza wartość, która ma być osiągalna. Jeśli wystąpi ten błąd, zazwyczaj oznacza to, że return brakuje instrukcji (§13.10.5).

Bloki 13.3

13.3.1 Ogólne

Blok zezwala na pisanie wielu instrukcji w kontekstach, w których dozwolona jest pojedyncza instrukcja.

block
    : '{' statement_list? '}'
    ;

Blok składa się z opcjonalnego statement_list (§13.3.2), ujętego w nawiasy klamrowe. Jeśli lista instrukcji zostanie pominięta, blok jest mówi się, że jest pusty.

Blok może zawierać oświadczenia deklaracji (§13.6). Zakres zmiennej lokalnej lub stałej zadeklarowanej w bloku jest blokiem.

Blok jest wykonywany w następujący sposób:

  • Jeśli blok jest pusty, kontrolka jest przenoszona do punktu końcowego bloku.
  • Jeśli blok nie jest pusty, kontrolka zostanie przeniesiona na listę instrukcji. Gdy kontrolka i jeśli osiągnie punkt końcowy listy instrukcji, kontrolka zostanie przeniesiona do punktu końcowego bloku.

Lista instrukcji bloku jest osiągalna, jeśli sam blok jest osiągalny.

Punkt końcowy bloku jest osiągalny, jeśli blok jest pusty lub punkt końcowy listy instrukcji jest osiągalny.

Blok zawierający co najmniej jedną yield instrukcję (§13.15) jest nazywany blokiem iteratora. Bloki iteracyjne służą do implementowania składowych funkcji jako iteratorów (§15.14). Niektóre dodatkowe ograniczenia dotyczą bloków iteratora:

  • Jest to błąd czasu kompilacji instrukcji return wyświetlanej w bloku iteratora (ale yield return instrukcje są dozwolone).
  • Jest to błąd czasu kompilacji dla bloku iteratora zawierającego niebezpieczny kontekst (§23.2). Blok iteratora zawsze definiuje bezpieczny kontekst, nawet jeśli jego deklaracja jest zagnieżdżona w niebezpiecznym kontekście.

13.3.2 Listy instrukcji

Lista instrukcji składa się z co najmniej jednej instrukcji napisanej w sekwencji. Listy instrukcji występują w blokach(§13.3) i w switch_blocks (§13.8.3).

statement_list
    : statement+
    ;

Lista instrukcji jest wykonywana przez przeniesienie kontrolki do pierwszej instrukcji. Gdy kontrolka i jeśli osiągnie punkt końcowy instrukcji, kontrolka zostanie przeniesiona do następnej instrukcji. Gdy kontrolka i jeśli osiągnie punkt końcowy ostatniej instrukcji, kontrolka zostanie przeniesiona do punktu końcowego listy instrukcji.

Instrukcja na liście instrukcji jest osiągalna, jeśli co najmniej jedna z następujących wartości jest prawdziwa:

  • Instrukcja jest pierwszą instrukcją, a sama lista instrukcji jest osiągalna.
  • Punkt końcowy poprzedniej instrukcji jest osiągalny.
  • Instrukcja jest instrukcją oznaczona etykietą, a etykieta jest przywoływane przez osiągalną goto instrukcję.

Punkt końcowy listy instrukcji jest osiągalny, jeśli punkt końcowy ostatniej instrukcji na liście jest osiągalny.

13.4 Pusta instrukcja

Empty_statement nic nie robi.

empty_statement
    : ';'
    ;

Pusta instrukcja jest używana, gdy nie ma żadnych operacji do wykonania w kontekście, w którym jest wymagana instrukcja.

Wykonanie pustej instrukcji po prostu przenosi kontrolkę do punktu końcowego instrukcji. W związku z tym punkt końcowy pustej instrukcji jest osiągalny, jeśli pusta instrukcja jest osiągalna.

Przykład: Pusta instrukcja może być używana podczas pisania while instrukcji z treścią o wartości null:

bool ProcessMessage() {...}
void ProcessMessages()
{
    while (ProcessMessage())
        ;
}

Ponadto pusta instrukcja może służyć do deklarowania etykiety tuż przed zamknięciem} "" bloku:

void F(bool done)
{
    ...
    if (done)
    {
        goto exit;
    }
    ...
  exit:
    ;
}

przykład końcowy

13.5 Instrukcje oznaczone etykietą

Labeled_statement zezwala na prefiks instrukcji z etykietą. Instrukcje oznaczone etykietami są dozwolone w blokach, ale nie są dozwolone jako instrukcje osadzone.

labeled_statement
    : identifier ':' statement
    ;

Instrukcja oznaczona etykietą deklaruje etykietę o nazwie nadanej przez identyfikator. Zakres etykiety to cały blok, w którym jest zadeklarowana etykieta, w tym wszystkie zagnieżdżone bloki. Jest to błąd czasu kompilacji dla dwóch etykiet o tej samej nazwie, aby mieć nakładające się zakresy.

Etykietę można przywoływać z goto instrukcji (§13.10.4) w zakresie etykiety.

Uwaga: oznacza to, że goto instrukcje mogą przenosić kontrolę wewnątrz bloków i z bloków, ale nigdy nie do bloków. notatka końcowa

Etykiety mają własną przestrzeń deklaracji i nie zakłócają innych identyfikatorów.

Przykład: przykład

int F(int x)
{
    if (x >= 0)
    {
        goto x;
    }
    x = -x;
  x:
    return x;
}

jest prawidłowy i używa nazwy x zarówno jako parametru, jak i etykiety.

przykład końcowy

Wykonanie instrukcji oznaczonej etykietą odpowiada dokładnie wykonaniu instrukcji po etykiecie.

Oprócz osiągalności zapewnianej przez normalny przepływ sterowania instrukcja oznaczona etykietą jest osiągalna, jeśli etykieta jest przywoływany przez instrukcję osiągalną goto , chyba że goto instrukcja znajduje się wewnątrz try bloku lub catch bloku try_statement , który zawiera finally blok, którego punkt końcowy jest nieosiągalny, a instrukcja oznaczona etykietą znajduje się poza try_statement.

Instrukcje deklaracji 13.6

13.6.1 Ogólne

Declaration_statement deklaruje co najmniej jedną zmienną lokalną, co najmniej jedną stałą lokalną lub funkcję lokalną. Instrukcje deklaracji są dozwolone w blokach i blokach przełączników, ale nie są dozwolone jako instrukcje osadzone.

declaration_statement
    : local_variable_declaration ';'
    | local_constant_declaration ';'
    | local_function_declaration
    ;

Zmienna lokalna jest deklarowana przy użyciu local_variable_declaration (§13.6.2). Stała lokalna jest deklarowana przy użyciu local_constant_declaration (§13.6.3). Funkcja lokalna jest deklarowana przy użyciu local_function_declaration (§13.6.4).

Zadeklarowane nazwy są wprowadzane do najbliższej otaczającej przestrzeni deklaracji (§7.3).

13.6.2 Deklaracje zmiennych lokalnych

13.6.2.1 Ogólne

Local_variable_declaration deklaruje co najmniej jedną zmienną lokalną.

local_variable_declaration
    : implicitly_typed_local_variable_declaration
    | explicitly_typed_local_variable_declaration
    | explicitly_typed_ref_local_variable_declaration
    ;

Niejawnie wpisane deklaracje zawierają słowo kluczowe kontekstowe (§6.4.4), var co powoduje niejednoznaczność składni między trzema kategoriami, które są rozpoznawane w następujący sposób:

  • Jeśli nie ma typu o nazwie var w zakresie, a dane wejściowe są zgodne implicitly_typed_local_variable_declaration , zostanie wybrany;
  • W przeciwnym razie, jeśli typ o nazwie var jest w zakresie, implicitly_typed_local_variable_declaration nie jest uważany za możliwe dopasowanie.

W local_variable_declaration każda zmienna jest wprowadzana przez deklarator, który jest jednym z implicitly_typed_local_variable_declarator, explicitly_typed_local_variable_declarator lub ref_local_variable_declarator dla niejawnie wpisanych, jawnie wpisanych i ref zmiennych lokalnych. Deklarator definiuje nazwę (identyfikator) i wartość początkową , jeśli istnieje, wprowadzonej zmiennej.

Jeśli w deklaracji istnieje wiele deklaratorów, są one przetwarzane, w tym wszelkie wyrażenia inicjacyjne, w kolejności od lewej do prawej (§9.4.4.5).

Uwaga: w przypadku local_variable_declaration, które nie występują jako for_initializer (§13.9.4) lub resource_acquisition (§13.14), ta lewa do prawej kolejności jest równoważna każdemu deklaratorowi znajdującemu się w osobnym local_variable_declaration. Na przykład:

void F()
{
    int x = 1, y, z = x * 2;
}

jest odpowiednikiem:

void F()
{
    int x = 1;
    int y;
    int z = x * 2;
}

notatka końcowa

Wartość zmiennej lokalnej jest uzyskiwana w wyrażeniu przy użyciu simple_name (§12.8.4). Zmienna lokalna musi być zdecydowanie przypisana (§9.4) w każdej lokalizacji, w której uzyskuje się jej wartość. Każda zmienna lokalna wprowadzona przez local_variable_declaration jest początkowo nieprzypisane (§9.4.3). Jeśli deklarator ma wyrażenie inicjacyjne, wprowadzona zmienna lokalna jest klasyfikowana jako przypisana na końcu deklaratora (§9.4.4.5).

Zakres zmiennej lokalnej wprowadzonej przez local_variable_declaration jest definiowany w następujący sposób (§7.7):

  • Jeśli deklaracja występuje jako for_initializer , zakresem jest for_initializer, for_condition, for_iterator i embedded_statement (§13.9.4);
  • Jeśli deklaracja występuje jako resource_acquisition zakres jest najbardziej zewnętrznym blokiem semantycznie równoważnego rozszerzenia using_statement (§13.14);
  • W przeciwnym razie zakres to blok, w którym występuje deklaracja.

Jest to błąd odwoływania się do zmiennej lokalnej według nazwy w pozycji tekstowej, która poprzedza deklaratora lub w dowolnym wyrażeniu inicjującym w deklaratorze. W zakresie zmiennej lokalnej jest to błąd czasu kompilacji, aby zadeklarować inną zmienną lokalną, funkcję lokalną lub stałą o tej samej nazwie.

Kontekst ref-safe-context (§9.7.2) zmiennej lokalnej ref jest kontekstem ref-safe-context inicjowania variable_reference. Kontekst ref-safe-context zmiennych lokalnych innych niż ref jest blok-deklaracji.

13.6.2.2 Niejawnie wpisane deklaracje zmiennych lokalnych

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
    ;

Implicitly_typed_local_variable_declaration wprowadza pojedynczą zmienną lokalną, identyfikator. Wyrażenie lub variable_reference musi mieć typ czasu kompilacji, T. Pierwsza alternatywa deklaruje zmienną z początkową wartością wyrażenia; jego typem jest, gdy T jest T? typem referencyjnym bez wartości null, w przeciwnym razie jego typem jest T. Druga alternatywa deklaruje zmienną ref z początkową wartością ref variable_reference; jego typem jest, gdy T jest ref T? typem referencyjnym, który nie może zawierać wartości null, w przeciwnym razie jego typem jest ref T. (ref_kind jest opisany w §15.6.1.)

Przykład:

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;

Niejawnie typizowane deklaracje zmiennych lokalnych są dokładnie równoważne następującym jawnie wpisanym deklaracjom:

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;

Poniżej przedstawiono niepoprawne niejawnie wpisane deklaracje zmiennych lokalnych:

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

przykład końcowy

13.6.2.3 Jawnie wpisane deklaracje zmiennych lokalnych

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
    ;

Explicity_typed_local_variable_declaration wprowadza co najmniej jedną zmienną lokalną z określonym typem.

Jeśli local_variable_initializer jest obecny, jego typ jest odpowiedni zgodnie z regułami prostego przypisania (§12.21.2) lub inicjowania tablicy (§17.7), a jego wartość jest przypisywana jako początkowa wartość zmiennej.

13.6.2.4 Jawnie wpisane deklaracje zmiennych lokalnych ref

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
    ;

Inicjowanie variable_reference ma typ i spełnia te same wymagania co w przypadku przypisania ref (§12.21.3).

Jeśli ref_kind to ref readonly, zadeklarowane identyfikatory są odwołaniami do zmiennych, które są traktowane jako tylko do odczytu. W przeciwnym razie, jeśli ref_kind to ref, identyfikatory zadeklarowane są odwołaniami do zmiennych, które można zapisywać.

Jest to błąd czasu kompilacji, aby zadeklarować zmienną lokalną ref lub zmienną ref struct typu w metodzie zadeklarowanej przy użyciu method_modifier asynclub w iteratorze (§15.14).

13.6.3 Deklaracje stałych lokalnych

Local_constant_declaration deklaruje co najmniej jedną stałą lokalną.

local_constant_declaration
    : 'const' type constant_declarators
    ;

constant_declarators
    : constant_declarator (',' constant_declarator)*
    ;

constant_declarator
    : identifier '=' constant_expression
    ;

Typ local_constant_declaration określa typ stałych wprowadzonych przez deklarację. Po typie następuje lista constant_declarators, z których każda wprowadza nową stałą. Constant_declarator składa się z identyfikatora, który nazywa stałą, po której następuje token "=", a następnie constant_expression (§12.23), który daje wartość stałej.

Typ i constant_expression lokalnej deklaracji stałej są zgodne z tymi samymi zasadami co deklaracja stałej składowej (§15.4).

Wartość stałej lokalnej jest uzyskiwana w wyrażeniu przy użyciu simple_name (§12.8.4).

Zakres stałej lokalnej to blok, w którym występuje deklaracja. Jest to błąd podczas odwoływania się do stałej lokalnej w pozycji tekstowej, która poprzedza koniec constant_declarator.

Lokalna deklaracja stałej, która deklaruje wiele stałych, jest równoważna wielokrotnym deklaracjom pojedynczych stałych o tym samym typie.

13.6.4 Lokalne deklaracje funkcji

Local_function_declaration deklaruje funkcję lokalną.

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 ';'
    ;

Uwaga gramatyczny: W przypadku rozpoznawania local_function_body, jeśli stosowane są zarówno null_conditional_invocation_expression, jak i alternatywy wyrażeń, należy wybrać pierwszy. (§15.6.1)

Przykład: istnieją dwa typowe przypadki użycia funkcji lokalnych: metody iteracyjne i metody asynchroniczne. W metodach iteratora wszelkie wyjątki są obserwowane tylko podczas wywoływania kodu wyliczającego zwróconą sekwencję. W metodach asynchronicznych wszelkie wyjątki są obserwowane tylko wtedy, gdy zwracane zadanie jest oczekiwane. W poniższym przykładzie pokazano oddzielenie walidacji parametrów od implementacji iteratora przy użyciu funkcji lokalnej:

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;
        }
    }
}

przykład końcowy

Jeśli nie określono inaczej niżej, semantyka wszystkich elementów gramatycznych jest taka sama jak w przypadku method_declaration (§15.6.1), odczytywana w kontekście funkcji lokalnej zamiast metody.

Identyfikator local_function_declaration musi być unikatowy w swoim zadeklarowanym zakresie blokowym, w tym wszelkie otaczające przestrzenie deklaracji zmiennych lokalnych. Jedną z konsekwencji jest to, że przeciążone local_function_declarations są niedozwolone.

Local_function_declaration może zawierać jeden async modyfikator (§15.15) i jeden unsafe modyfikator (§23.1). Jeżeli deklaracja zawiera async modyfikator, typ zwracany musi być void lub typem «TaskType» (§15.15.1). Jeśli deklaracja zawiera static modyfikator, funkcja jest statyczną funkcją lokalną; w przeciwnym razie jest to funkcja niestatyczna lokalna. Jest to błąd czasu kompilacji dla type_parameter_list lub parameter_list zawierać atrybuty. Jeśli funkcja lokalna jest zadeklarowana w niebezpiecznym kontekście (§23.2), funkcja lokalna może zawierać niebezpieczny kod, nawet jeśli deklaracja funkcji lokalnej nie zawiera unsafe modyfikatora.

Funkcja lokalna jest zadeklarowana w zakresie bloku. Funkcja lokalna niestatyczna może przechwytywać zmienne z otaczającego zakresu, podczas gdy statyczna funkcja lokalna nie może (więc nie ma dostępu do ujęć lokalnych, parametrów, niestacjonanych funkcji lokalnych lub this). Jest to błąd czasu kompilacji, jeśli przechwycona zmienna jest odczytywana przez treść niestacjonanej funkcji lokalnej, ale nie jest zdecydowanie przypisana przed każdym wywołaniem funkcji. Kompilator określa, które zmienne są zdecydowanie przypisane po powrocie (§9.4.4.33).

Gdy typ jest typem this struktury, jest to błąd czasu kompilacji dla treści funkcji lokalnej w celu uzyskania dostępu do thiselementu . Jest to prawda, czy dostęp jest jawny (jak w this.xpliku ) lub niejawny (tak jak w przypadku, gdy x x jest członkiem wystąpienia struktury). Ta reguła uniemożliwia tylko taki dostęp i nie ma wpływu na to, czy wyszukiwanie elementów członkowskich powoduje element członkowski struktury.

Jest to błąd czasu kompilacji dla treści funkcji lokalnej zawierającej instrukcję, break instrukcję goto lub continue instrukcję, której element docelowy znajduje się poza treścią funkcji lokalnej.

Uwaga: powyższe reguły dla this funkcji anonimowych i goto dublowania ich w §12.19.3. notatka końcowa

Funkcja lokalna może być wywoływana z punktu leksyktycznego przed jego deklaracją. Jednak jest to błąd czasu kompilacji funkcji, który ma być zadeklarowany leksykalnie przed deklaracją zmiennej używanej w funkcji lokalnej (§7.7).

Jest to błąd czasu kompilacji dla funkcji lokalnej, aby zadeklarować parametr, parametr typu lub zmienną lokalną o tej samej nazwie co zadeklarowany w dowolnej otaczającej przestrzeni deklaracji zmiennej lokalnej.

Lokalne jednostki funkcji są zawsze dostępne. Punkt końcowy deklaracji funkcji lokalnej jest osiągalny, jeśli punkt początkowy deklaracji funkcji lokalnej jest osiągalny.

Przykład: W poniższym przykładzie treść L obiektu jest osiągalna, mimo że punkt L początkowy elementu nie jest osiągalny. Ponieważ punkt L początkowy elementu nie jest osiągalny, instrukcja po punkcie końcowym L nie jest osiągalna:

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;
    }
}

Innymi słowy, lokalizacja lokalnej deklaracji funkcji nie ma wpływu na osiągalność żadnych instrukcji w funkcji zawierającej. przykład końcowy

Jeśli typ argumentu funkcji lokalnej to dynamic, wywoływana funkcja jest rozpoznawana w czasie kompilacji, a nie w czasie wykonywania.

Funkcja lokalna nie może być używana w drzewie wyrażeń.

Statyczna funkcja lokalna

  • Może odwoływać się do statycznych elementów członkowskich, parametrów typu, stałych definicji i statycznych funkcji lokalnych z otaczającego zakresu.
  • Nie odwołuje this się ani base do elementów członkowskich wystąpienia z niejawnego this odwołania, ani zmiennych lokalnych, parametrów ani niestacjonowych funkcji lokalnych z otaczającego zakresu. Jednak wszystkie te elementy są dozwolone w wyrażeniu nameof() .

13.7 Instrukcje wyrażeń

Expression_statement oblicza dane wyrażenie. Wartość obliczona przez wyrażenie, jeśli istnieje, jest odrzucana.

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
    ;

Nie wszystkie wyrażenia są dozwolone jako instrukcje.

Uwaga: W szczególności wyrażenia takie jak x + y i x == 1, które tylko obliczają wartość (która zostanie odrzucona), nie są dozwolone jako instrukcje. notatka końcowa

Wykonanie expression_statement oblicza zawarte wyrażenie, a następnie przenosi kontrolkę do punktu końcowego expression_statement. Punkt końcowy expression_statement jest osiągalny, jeśli expression_statement jest osiągalny.

13.8 Instrukcje wyboru

13.8.1 Ogólne

Instrukcje wyboru wybierają jedną z wielu możliwych instrukcji do wykonania na podstawie wartości pewnego wyrażenia.

selection_statement
    : if_statement
    | switch_statement
    ;

13.8.2 Instrukcja if

Instrukcja if wybiera instrukcję do wykonania na podstawie wartości wyrażenia logicznego.

if_statement
    : 'if' '(' boolean_expression ')' embedded_statement
    | 'if' '(' boolean_expression ')' embedded_statement
      'else' embedded_statement
    ;

Część else jest skojarzona z najbliższą leksykalicznie wcześniejszą if , która jest dozwolona przez składnię.

Przykład: w związku z if tym instrukcja formularza

if (x) if (y) F(); else G();

jest równoważny

if (x)
{
    if (y)
    {
        F();
    }
    else
    {
        G();
    }
}

przykład końcowy

Instrukcja if jest wykonywana w następujący sposób:

  • Ocenia się boolean_expression (§12.24).
  • Jeśli wyrażenie logiczne zwróci wartość true, kontrolka zostanie przeniesiona do pierwszej osadzonej instrukcji. Kiedy i jeśli kontrolka osiągnie punkt końcowy tej instrukcji, kontrolka jest przenoszona do punktu końcowego if instrukcji.
  • Jeśli wyrażenie logiczne zwraca wartość false i jeśli else część jest obecna, kontrolka zostanie przeniesiona do drugiej osadzonej instrukcji. Kiedy i jeśli kontrolka osiągnie punkt końcowy tej instrukcji, kontrolka jest przenoszona do punktu końcowego if instrukcji.
  • Jeśli wyrażenie logiczne zwraca wartość false i jeśli else część nie jest obecna, kontrolka jest przenoszona do punktu końcowego if instrukcji .

Pierwsza osadzona instrukcja if jest osiągalna, jeśli if instrukcja jest osiągalna, a wyrażenie logiczne nie ma stałej wartości false.

Druga osadzona instrukcja if , jeśli jest obecna, jest osiągalna, jeśli if instrukcja jest osiągalna, a wyrażenie logiczne nie ma stałej wartości true.

Punkt końcowy instrukcji if jest osiągalny, jeśli punkt końcowy co najmniej jednego z osadzonych instrukcji jest osiągalny. Ponadto punkt końcowy instrukcji if bez części jest else osiągalny, jeśli if instrukcja jest osiągalna, a wyrażenie logiczne nie ma stałej wartości true.

13.8.3 Instrukcja switch

Instrukcja switch wybiera do wykonania listę instrukcji o skojarzonej etykiecie przełącznika, która odpowiada wartości wyrażenia switch.

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
    ;

Switch_statement składa się ze słowa kluczowego switch, po którym następuje wyrażenie nawiasowe (nazywane wyrażeniem przełącznika), a następnie switch_block. Switch_block składa się z zera lub większej liczby switch_sections, ujętej w nawiasy klamrowe. Każdy switch_section składa się z co najmniej jednego switch_label, po którym następuje statement_list (§13.3.2). Każda switch_label zawierająca case ma skojarzony wzorzec (§11), względem którego jest testowana wartość wyrażenia przełącznika. Jeśli case_guard jest obecny, jego wyrażenie jest niejawnie konwertowane na typ bool i wyrażenie to jest oceniane jako dodatkowy warunek, aby sprawa została uznana za spełnioną.

Typ zarządzający instrukcji switch jest ustanawiany przez wyrażenie switch.

  • Jeśli typem wyrażenia przełącznika jest sbyte, , byteushortintlongcharboolulongshortuintstringlub enum_type, lub jeśli jest to typ wartości dopuszczających wartość null odpowiadający jednemu z tych typów, jest to typ switch zarządzający instrukcji .
  • W przeciwnym razie, jeśli dokładnie jedna niejawna konwersja zdefiniowana przez użytkownika istnieje z typu wyrażenia przełącznika do jednego z następujących możliwych typów zarządzania: sbyte, , byte, intlongushortshortulongcharuintstringlub, typu wartości dopuszczanej do wartości null odpowiadającego jednemu z tych typów, przekonwertowany typ jest typem switch zarządzającym instrukcji.
  • W przeciwnym razie typ zarządzający instrukcji switch jest typem wyrażenia przełącznika. Jest to błąd, jeśli taki typ nie istnieje.

W instrukcji może znajdować się co najwyżej jedna default etykieta switch .

Jest to błąd, jeśli wzorzec jakiejkolwiek etykiety przełącznika nie ma zastosowania (§11.2.1) do typu wyrażenia wejściowego.

Jest to błąd, jeśli wzorzec dowolnej etykiety przełącznika jest subsumowany przez (§11.3) zestaw wzorców wcześniejszych etykiet przełącznika instrukcji switch, które nie mają osłony wielkości liter lub którego osłona wielkości liter jest stałym wyrażeniem o wartości true.

Przykład:

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.
}

przykład końcowy

Instrukcja switch jest wykonywana w następujący sposób:

  • Wyrażenie przełącznika jest obliczane i konwertowane na typ zarządzający.
  • Kontrolka jest przekazywana zgodnie z wartością przekonwertowanego wyrażenia przełącznika:
    • Pierwszy wzorzec leksykalnie w zestawie case etykiet w tej samej switch instrukcji, która pasuje do wartości wyrażenia przełącznika, i dla którego wyrażenie guard jest nieobecne lub daje w wyniku wartość true, powoduje przeniesienie kontrolki na listę instrukcji po dopasowanej case etykiecie.
    • W przeciwnym razie, jeśli etykieta jest obecna, kontrolka default zostanie przeniesiona na listę instrukcji po etykiecie default .
    • W przeciwnym razie kontrolka jest przenoszona do punktu końcowego instrukcji switch .

Uwaga: kolejność dopasowywania wzorców w czasie wykonywania nie jest zdefiniowana. Kompilator jest dozwolony (ale nie jest wymagany) do dopasowania wzorców poza kolejność i ponownego użycia wyników już dopasowanych wzorców w celu obliczenia wyniku dopasowania innych wzorców. Niemniej jednak kompilator jest wymagany do określenia leksykalnie pierwszego wzorca zgodnego z wyrażeniem i dla którego klauzula guard jest nieobecna lub ocenia wartość true. notatka końcowa

Jeśli punkt końcowy listy instrukcji sekcji przełącznika jest osiągalny, wystąpi błąd czasu kompilacji. Jest to nazywane regułą "no fall through".

Przykład: przykład

switch (i)
{
    case 0:
        CaseZero();
        break;
    case 1:
        CaseOne();
        break;
    default:
        CaseOthers();
        break;
}

jest prawidłowa, ponieważ żadna sekcja przełącznika nie ma osiągalnego punktu końcowego. W przeciwieństwie do języka C i C++, wykonanie sekcji przełącznika nie może "przełączyć się" do następnej sekcji przełącznika i przykład

switch (i)
{
    case 0:
        CaseZero();
    case 1:
        CaseZeroOrOne();
    default:
        CaseAny();
}

powoduje wystąpienie błędu czasu kompilacji. Po wykonaniu sekcji przełącznika należy wykonać inną sekcję przełącznika, należy użyć jawnej goto case instrukcji lub goto default :

switch (i)
{
    case 0:
        CaseZero();
        goto case 1;
    case 1:
        CaseZeroOrOne();
        goto default;
    default:
        CaseAny();
        break;
}

przykład końcowy

Wiele etykiet jest dozwolonych w switch_section.

Przykład: przykład

switch (i)
{
    case 0:
        CaseZero();
        break;
    case 1:
        CaseOne();
        break;
    case 2:
    default:
        CaseTwo();
        break;
}

jest prawidłowa. Przykład nie narusza reguły "no fall through", ponieważ etykiety case 2: i default: są częścią tego samego switch_section.

przykład końcowy

Uwaga: reguła "no fall through" uniemożliwia typową klasę usterek występujących w języku C i C++, gdy break instrukcje zostaną przypadkowo pominięte. Na przykład sekcje powyższej switch instrukcji można odwrócić bez wpływu na zachowanie instrukcji:

switch (i)
{
    default:
        CaseAny();
        break;
    case 1:
        CaseZeroOrOne();
        goto default;
    case 0:
        CaseZero();
        goto case 1;
}

notatka końcowa

Uwaga: lista instrukcji sekcji przełącznika zwykle kończy się instrukcją break, goto caselub goto default , ale każda konstrukcja, która renderuje punkt końcowy listy instrukcji jest dozwolony. Na przykład instrukcja kontrolowana while przez wyrażenie true logiczne jest znana, aby nigdy nie osiągać punktu końcowego. Podobnie instrukcja or return zawsze przenosi kontrolkę throw gdzie indziej i nigdy nie osiąga punktu końcowego. W związku z tym następujący przykład jest prawidłowy:

switch (i)
{
     case 0:
         while (true)
         {
             F();
         }
     case 1:
         throw new ArgumentException();
     case 2:
         return;
}

notatka końcowa

Przykład: Typ zarządzający instrukcji może być typem switch string. Na przykład:

void DoCommand(string command)
{
    switch (command.ToLower())
    {
        case "run":
            DoRun();
            break;
        case "save":
            DoSave();
            break;
        case "quit":
            DoQuit();
            break;
        default:
            InvalidCommand(command);
            break;
    }
}

przykład końcowy

Uwaga: Podobnie jak operatory równości ciągów (§12.12.8), instrukcja uwzględnia wielkość liter i wykonuje daną sekcję przełącznika tylko wtedy, switch gdy ciąg wyrażenia przełącznika dokładnie pasuje do stałej case etykiety. end note Jeśli typ zarządzający instrukcji switch jest string lub typ wartości dopuszczanej do wartości null, wartość null jest dozwolona case jako stała etykiety.

Statement_list s switch_block może zawierać oświadczenia deklaracji (§13.6). Zakres zmiennej lokalnej lub stałej zadeklarowanej w bloku przełącznika to blok przełącznika.

Etykieta przełącznika jest osiągalna, jeśli co najmniej jedna z następujących wartości jest prawdziwa:

  • Wyrażenie przełącznika jest stałą wartością i albo
    • etykieta jest wzorcem case , którego wzorzec będzie zgodny (§11.2.1) tej wartości, a ochrona etykiety jest nieobecna lub nie jest wyrażeniem stałym z wartością false; lub
    • jest to etykieta default , a żadna sekcja przełącznika nie zawiera etykiety wielkości liter, której wzorzec będzie pasował do tej wartości i którego ochrona jest nieobecna lub wyrażenie stałe z wartością true.
  • Wyrażenie przełącznika nie jest wartością stałą i albo
    • etykieta case jest bez ochrony lub z strażnikiem, którego wartość nie jest stałą wartością false; lub
    • jest to etykieta default i
      • zestaw wzorców pojawiających się wśród przypadków instrukcji przełącznika, które nie mają strażników lub mają strażników, których wartość jest stałą true, nie jest wyczerpująca (§11.4) dla typu zarządzającego przełącznikiem; lub
      • typ zarządzający przełącznikiem jest typem dopuszczającym wartość null i zestawem wzorców wyświetlanych wśród przypadków instrukcji switch, które nie mają osłon lub mają osłony, których wartość jest stałą true, nie zawiera wzorca, który pasuje do wartości null.
  • Etykieta przełącznika jest przywoływalna przez instrukcję lub goto default osiągalnągoto case.

Lista instrukcji danej sekcji przełącznika jest osiągalna, jeśli switch instrukcja jest osiągalna, a sekcja przełącznika zawiera osiągalną etykietę przełącznika.

Punkt końcowy instrukcji switch jest osiągalny, jeśli instrukcja switch jest osiągalna, a co najmniej jedna z następujących wartości jest prawdziwa:

  • Instrukcja switch zawiera osiągalną break instrukcję, która kończy instrukcję switch .
  • Etykieta nie default jest obecna i albo
    • Wyrażenie przełącznika jest wartością niestałych, a zestaw wzorców wyświetlanych wśród przypadków instrukcji switch, które nie mają osłon lub mają osłony, których wartość jest stałą true, nie jest wyczerpująca (§11.4) dla typu zarządzającego przełącznikiem.
    • Wyrażenie przełącznika jest niestałych wartości typu dopuszczającego wartość null i nie występuje wzorzec wśród przypadków instrukcji switch, które nie mają strażników lub mają strażników, których wartość jest stałą true, będzie zgodna z wartością null.
    • Wyrażenie przełącznika jest stałą wartością i żadna case etykieta bez ochrony lub której ochrona jest stałą true, będzie zgodna z tej wartości.

Przykład: Poniższy kod przedstawia zwięzłe użycie klauzuli when :

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";
    }
}

Przypadek wariancja jest zgodny nullz ciągiem pustym lub dowolnym ciągiem zawierającym tylko białe znaki. przykład końcowy

13.9 Instrukcje iteracji

13.9.1 Ogólne

Instrukcje iteracji wielokrotnie wykonują instrukcję osadzoną.

iteration_statement
    : while_statement
    | do_statement
    | for_statement
    | foreach_statement
    ;

13.9.2 Instrukcja while

Instrukcja while warunkowo wykonuje osadzoną instrukcję zero lub więcej razy.

while_statement
    : 'while' '(' boolean_expression ')' embedded_statement
    ;

Instrukcja while jest wykonywana w następujący sposób:

  • Ocenia się boolean_expression (§12.24).
  • Jeśli wyrażenie logiczne zwróci wartość true, kontrolka zostanie przeniesiona do instrukcji embedded. Kiedy i jeśli kontrolka osiągnie punkt końcowy osadzonej instrukcji (prawdopodobnie z wykonania continue instrukcji), kontrolka jest przenoszona na początek instrukcji while .
  • Jeśli wyrażenie logiczne zwraca wartość false, kontrolka jest przenoszona do punktu końcowego instrukcji while .

W osadzonej while break instrukcji instrukcja (§13.10.2) może służyć do przenoszenia kontroli do punktu while końcowego instrukcji (w ten sposób kończąca iterację osadzonej instrukcji) i continue instrukcji (§13.10.3) może służyć do przenoszenia kontroli do punktu końcowego osadzonej instrukcji (w związku z tym wykonywanie kolejnej iteracji while instrukcji).

Osadzona instrukcja while jest osiągalna, jeśli while instrukcja jest osiągalna, a wyrażenie logiczne nie ma stałej wartości false.

Punkt końcowy instrukcji while jest osiągalny, jeśli spełniony jest co najmniej jeden z następujących warunków:

  • Instrukcja while zawiera osiągalną break instrukcję, która kończy instrukcję while .
  • Instrukcja while jest osiągalna, a wyrażenie logiczne nie ma stałej wartości true.

13.9.3 Instrukcja do

Instrukcja do warunkowo wykonuje osadzoną instrukcję co najmniej raz.

do_statement
    : 'do' embedded_statement 'while' '(' boolean_expression ')' ';'
    ;

Instrukcja do jest wykonywana w następujący sposób:

  • Kontrolka jest przenoszona do instrukcji embedded.
  • Kiedy i jeśli kontrolka osiągnie punkt końcowy osadzonej instrukcji (prawdopodobnie z wykonania continue instrukcji), boolean_expression (§12.24) jest obliczany. Jeśli wyrażenie logiczne zwraca wartość true, kontrolka zostanie przeniesiona na początek instrukcji do . W przeciwnym razie kontrolka jest przenoszona do punktu końcowego instrukcji do .

W osadzonej do break instrukcji instrukcja (§13.10.2) może służyć do przenoszenia kontroli do punktu do końcowego instrukcji (w ten sposób kończąca iterację osadzonej instrukcji) i continue instrukcji (§13.10.3) może służyć do przenoszenia kontroli do punktu końcowego osadzonej instrukcji (w związku z tym wykonywanie kolejnej iteracji do instrukcji).

Osadzona instrukcja instrukcji do jest osiągalna, jeśli do instrukcja jest osiągalna.

Punkt końcowy instrukcji do jest osiągalny, jeśli spełniony jest co najmniej jeden z następujących warunków:

  • Instrukcja do zawiera osiągalną break instrukcję, która kończy instrukcję do .
  • Punkt końcowy osadzonej instrukcji jest osiągalny, a wyrażenie logiczne nie ma stałej wartości true.

13.9.4 Instrukcja for

Instrukcja for oblicza sekwencję wyrażeń inicjalizacji, a następnie, gdy warunek jest spełniony, wielokrotnie wykonuje osadzoną instrukcję i ocenia sekwencję wyrażeń iteracji.

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)*
    ;

For_initializer, jeśli istnieje, składa się z local_variable_declaration (§13.6.2) lub listy statement_expression s (§13.7) oddzielonych przecinkami. Zakres zmiennej lokalnej zadeklarowanej przez for_initializer to for_initializer, for_condition, for_iterator i embedded_statement.

For_condition, jeśli istnieje, jest boolean_expression (§12.24).

For_iterator, jeśli jest obecny, składa się z listy statement_expressions (§13.7) rozdzielonych przecinkami.

Instrukcja for jest wykonywana w następujący sposób:

  • Jeśli for_initializer jest obecny, inicjatory zmiennych lub wyrażenia instrukcji są wykonywane w kolejności ich zapisu. Ten krok jest wykonywany tylko raz.
  • Jeśli for_condition jest obecny, jest obliczany.
  • Jeśli for_condition nie istnieje lub jeśli ocena daje wartość true, kontrola zostanie przeniesiona do instrukcji osadzonej. Kiedy i jeśli kontrolka osiągnie punkt końcowy osadzonej instrukcji (prawdopodobnie z wykonania continue instrukcji), wyrażenia for_iterator, jeśli istnieją, są oceniane w sekwencji, a następnie wykonywana jest kolejna iteracja, począwszy od oceny for_condition w powyższym kroku.
  • Jeśli for_condition jest obecny, a ocena daje wartość false, kontrola jest przekazywana do punktu końcowego for instrukcji.

W osadzonej for break instrukcji instrukcja (§13.10.2) może służyć do przenoszenia kontroli do punktu for końcowego instrukcji (w ten sposób kończąca iterację osadzonej instrukcji) i continue instrukcji (§13.10.3) może służyć do przenoszenia kontroli do punktu końcowego osadzonej instrukcji (w ten sposób wykonywania for_iterator i wykonywania innej iteracji for instrukcji, począwszy od for_condition).

Osadzona instrukcja for jest osiągalna, jeśli jedna z następujących wartości jest prawdziwa:

  • Instrukcja for jest osiągalna i nie ma for_condition .
  • Instrukcja for jest osiągalna, a for_condition jest obecny i nie ma stałej wartości false.

Punkt końcowy instrukcji for jest osiągalny, jeśli spełniony jest co najmniej jeden z następujących warunków:

  • Instrukcja for zawiera osiągalną break instrukcję, która kończy instrukcję for .
  • Instrukcja for jest osiągalna, a for_condition jest obecny i nie ma stałej wartości true.

13.9.5 Instrukcja foreach

Instrukcja foreach wylicza elementy kolekcji, wykonując instrukcję osadzoną dla każdego elementu kolekcji.

foreach_statement
    : 'foreach' '(' ref_kind? local_variable_type identifier 'in' 
      expression ')' embedded_statement
    ;

Local_variable_type i identyfikator instrukcji foreach deklarują zmienną iteracji instrukcji. var Jeśli identyfikator jest podany jako local_variable_type, a żaden typ o nazwie var nie jest w zakresie, zmienna iteracji jest określana jako niejawnie typ zmiennej iteracji, a jej typ jest traktowany jako typ foreach elementu instrukcji, jak określono poniżej.

Jeśli foreach_statement zawiera zarówno, jak i ref , readonlyzmienna iteracji oznacza zmienną, która jest traktowana jako tylko do odczytu. W przeciwnym razie, jeśli foreach_statement zawiera ref bez readonly, zmienna iteracji oznacza zmienną, która może być zapisywalna.

Zmienna iteracji odpowiada zmiennej lokalnej z zakresem rozszerzającym się na instrukcję osadzoną. Podczas wykonywania instrukcji foreach zmienna iteracji reprezentuje element kolekcji, dla którego jest obecnie wykonywana iteracja. Jeśli zmienna iteracji oznacza zmienną tylko do odczytu, występuje błąd czasu kompilacji, jeśli osadzona instrukcja próbuje zmodyfikować ją (za pośrednictwem przypisania lub ++ operatorów i -- ) lub przekazać ją jako odwołanie lub parametr wyjściowy.

W poniższym celu, aby uzyskać zwięzłość, IEnumerable, IEnumeratorIEnumerable<T> i IEnumerator<T> odwołaj się do odpowiednich typów w przestrzeniach System.Collections nazw i System.Collections.Generic.

Przetwarzanie w czasie kompilacji instrukcji foreach najpierw określa typ kolekcji, typ modułu wyliczającego i typ iteracji wyrażenia. Ta determinacja jest kontynuowana w następujący sposób:

  • Jeśli typ wyrażenia jest typem X tablicy, istnieje niejawna konwersja odwołania z X na IEnumerable interfejs (ponieważ System.Array implementuje ten interfejs). Typ kolekcji jest interfejsem, typem IEnumerable modułu wyliczającego jest IEnumerator interfejs, a typ iteracji jest typem elementu typu Xtablicy .
  • Jeśli typ X wyrażenia to, istnieje niejawna konwersja z wyrażenia na IEnumerable interfejs (§10.2.10).dynamic Typ kolekcji jest interfejsem IEnumerable , a typem modułu IEnumerator wyliczającego jest interfejs. var Jeśli identyfikator jest podany jako local_variable_type, typ iteracji to dynamic, w przeciwnym razie jest objectto .
  • W przeciwnym razie określ, czy typ X ma odpowiednią GetEnumerator metodę:
    • Przeprowadź wyszukiwanie składowych dla typu X z identyfikatorem GetEnumerator i bez argumentów typu. Jeśli wyszukiwanie elementu członkowskiego nie generuje dopasowania lub generuje niejednoznaczność lub tworzy dopasowanie, które nie jest grupą metod, sprawdź interfejs wyliczalny zgodnie z poniższym opisem. Zaleca się, aby ostrzeżenie zostało wydane, jeśli wyszukiwanie elementu członkowskiego generuje dowolne elementy, z wyjątkiem grupy metod lub braku dopasowania.
    • Przeprowadź rozpoznawanie przeciążeń przy użyciu wynikowej grupy metod i pustej listy argumentów. Jeśli rozpoznawanie przeciążenia nie powoduje zastosowania metod, powoduje niejednoznaczność lub powoduje utworzenie jednej najlepszej metody, ale ta metoda jest statyczna lub nie jest publiczna, sprawdź interfejs wyliczalny, jak opisano poniżej. Zaleca się, aby ostrzeżenie zostało wydane, jeśli rozwiązanie przeciążenia generuje dowolne elementy, z wyjątkiem jednoznacznej metody wystąpienia publicznego lub nie ma odpowiednich metod.
    • Jeśli zwracany typ E GetEnumerator metody nie jest klasą, strukturą lub typem interfejsu, zostanie wygenerowany błąd i nie zostaną podjęte żadne dalsze kroki.
    • Wyszukiwanie składowe jest wykonywane przy E użyciu identyfikatora Current i bez argumentów typu. Jeśli wyszukiwanie elementu członkowskiego nie daje dopasowania, wynik jest błędem lub wynikiem jest wszystko, z wyjątkiem właściwości wystąpienia publicznego, która zezwala na odczyt, zostanie wygenerowany błąd i nie zostaną podjęte żadne dalsze kroki.
    • Wyszukiwanie składowe jest wykonywane przy E użyciu identyfikatora MoveNext i bez argumentów typu. Jeśli wyszukiwanie składowe nie daje dopasowania, wynik jest błędem lub wynikiem jest coś poza grupą metod, generowany jest błąd i nie są wykonywane żadne dalsze kroki.
    • Rozpoznawanie przeciążenia jest wykonywane w grupie metod z pustą listą argumentów. Jeśli rozpoznawanie przeciążenia nie powoduje żadnych odpowiednich metod, powoduje niejednoznaczność lub powoduje utworzenie jednej najlepszej metody, ale ta metoda jest statyczna lub nie jest publiczna lub jej typ zwracany nie booljest , zostanie wygenerowany błąd i nie zostaną wykonane żadne dalsze kroki.
    • Typ kolekcji to X, typ modułu wyliczającego to E, a typ iteracji jest typem Current właściwości. Właściwość Current może zawierać ref modyfikator, w którym przypadku zwracane wyrażenie jest variable_reference (§9.5), który jest opcjonalnie tylko do odczytu.
  • W przeciwnym razie sprawdź interfejs wyliczalny:
    • Jeśli wśród wszystkich typówTᵢ, dla których istnieje niejawna konwersja z X do IEnumerable<Tᵢ>, istnieje unikatowy typT, który T nie dynamic jest i dla wszystkich pozostałych Tᵢ istnieje niejawna konwersja z IEnumerable<T> do IEnumerable<Tᵢ>, typ kolekcji jest interfejs , typ wyliczający jest interfejs IEnumerable<T>IEnumerator<T>, a typ iteracji to T.
    • W przeciwnym razie, jeśli istnieje więcej niż jeden taki typ T, zostanie wygenerowany błąd i nie zostaną wykonane żadne dalsze kroki.
    • W przeciwnym razie, jeśli istnieje niejawna konwersja z do interfejsuSystem.Collections.IEnumerable, typ kolekcji jest tym interfejsem, typ modułu wyliczającego to interfejs System.Collections.IEnumerator, a typ iteracji to object.X
    • W przeciwnym razie zostanie wygenerowany błąd i nie zostaną podjęte żadne dalsze kroki.

Powyższe kroki, jeśli zakończyły się pomyślnie, jednoznacznie tworzą typ kolekcji , typ Cmodułu wyliczającego i typ E Titeracji , ref Tlub ref readonly T. Instrukcja foreach formularza

foreach (V v in x) «embedded_statement»

element jest wówczas odpowiednikiem:

{
    E e = ((C)(x)).GetEnumerator();
    try
    {
        while (e.MoveNext())
        {
            V v = (V)(T)e.Current;
            «embedded_statement»
        }
    }
    finally
    {
        ... // Dispose e
    }
}

Zmienna e nie jest widoczna ani dostępna dla wyrażenia x , instrukcji osadzonej ani żadnego innego kodu źródłowego programu. Zmienna v jest tylko do odczytu w instrukcji embedded. Jeśli nie ma jawnej konwersji (§10.3) z T (typu iteracji) do V ( local_variable_type w foreach instrukcji), zostanie wygenerowany błąd i nie zostaną wykonane żadne dalsze kroki.

Gdy zmienna iteracji jest zmienną referencyjną (§9.7), foreach instrukcja formularza

foreach (ref V v in x) «embedded_statement»

element jest wówczas odpowiednikiem:

{
    E e = ((C)(x)).GetEnumerator();
    try
    {
        while (e.MoveNext())
        {
            ref V v = ref e.Current;
            «embedded_statement»
        }
    }
    finally
    {
        ... // Dispose e
    }
}

Zmienna e nie jest widoczna ani dostępna dla wyrażenia x , instrukcji osadzonej lub innego kodu źródłowego programu. Zmienna v referencyjna jest zapisem w osadzonej instrukcji, ale v nie jest ponownie przypisana (§12.21.3). Jeśli nie istnieje konwersja tożsamości (§10.2.2) z T (typ iteracji) na V ( local_variable_type w foreach instrukcji), zostanie wygenerowany błąd i nie zostaną wykonane żadne dalsze kroki.

foreach Instrukcja formularza foreach (ref readonly V v in x) «embedded_statement» ma podobną równoważną formę, ale zmienna v referencyjna znajduje ref readonly się w instrukcji osadzonej i w związku z tym nie może zostać ponownie przypisana ani ponownie przypisana.

Uwaga: jeśli x ma wartość null, System.NullReferenceException element jest zgłaszany w czasie wykonywania. notatka końcowa

Implementacja jest dozwolona do implementacji danego foreach_statement inaczej, np. ze względów wydajności, o ile zachowanie jest zgodne z powyższym rozszerzeniem.

Umieszczenie v wewnątrz while pętli jest ważne dla sposobu przechwytywania (§12.19.6.2) przez dowolną funkcję anonimową występującą w embedded_statement.

Przykład:

int[] values = { 7, 9, 13 };
Action f = null;
foreach (var value in values)
{
    if (f == null)
    {
        f = () => Console.WriteLine("First value: " + value);
    }
}
f();

Jeśli v w formularzu rozwiniętym został zadeklarowany poza pętlą while , będzie on współużytkowany wśród wszystkich iteracji, a jego wartość po for pętli będzie wartością końcową , 13czyli tym, co wywołanie f zostanie wydrukowane. Zamiast tego, ponieważ każda iteracja ma własną zmienną v, ta przechwycona f w pierwszej iteracji będzie nadal przechowywać wartość 7, która będzie drukowana. (Należy pamiętać, że wcześniejsze wersje języka C# zadeklarowane v poza pętlą while ).

przykład końcowy

Treść finally bloku jest skonstruowana zgodnie z następującymi krokami:

  • Jeśli istnieje niejawna konwersja z E do interfejsu, wówczas System.IDisposable

    • Jeśli E jest typem wartości innej niż null, klauzula finally zostanie rozszerzona do semantycznego odpowiednika:

      finally
      {
          ((System.IDisposable)e).Dispose();
      }
      
    • W przeciwnym razie klauzula finally jest rozszerzana na semantyczny odpowiednik:

      finally
      {
          System.IDisposable d = e as System.IDisposable;
          if (d != null)
          {
              d.Dispose();
          }
      }
      

      z wyjątkiem tego, że jeśli E jest typem wartości lub parametrem typu utworzonego w typie wartości, konwersja parametru e na System.IDisposable nie powoduje wystąpienia pola.

  • W przeciwnym razie, jeśli E jest typem zapieczętowanym, klauzula finally zostanie rozszerzona do pustego bloku:

    finally {}
    
  • W przeciwnym razie klauzula finally jest rozszerzana na:

    finally
    {
        System.IDisposable d = e as System.IDisposable;
        if (d != null)
        {
            d.Dispose();
        }
    }
    

Zmienna d lokalna nie jest widoczna ani dostępna dla żadnego kodu użytkownika. W szczególności nie powoduje konfliktu z żadną inną zmienną, której zakres obejmuje finally blok.

Kolejność foreach przechodzenia przez elementy tablicy jest następująca: w przypadku elementów tablic jednowymiarowych następuje przechodzenie w kolejności rosnącej indeksu, począwszy od indeksu 0 i kończąc na indeksie Length – 1. W przypadku tablic wielowymiarowych elementy są przechodzine tak, aby indeksy wymiaru z prawej strony zostały najpierw zwiększone, a następnie następny wymiar po lewej stronie itd.

Przykład: Poniższy przykład wyświetla każdą wartość w tablicy dwuwymiarowej w kolejności elementów:

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();
    }
}

Wygenerowane dane wyjściowe są następujące:

1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9

przykład końcowy

Przykład: w poniższym przykładzie

int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers)
{
    Console.WriteLine(n);
}

typ n jest wnioskowany jako int, typ iteracji .numbers

przykład końcowy

13.10 Instrukcje Jump

13.10.1 Ogólne

Skocz oświadczenia bezwarunkowo kontroli transferu.

jump_statement
    : break_statement
    | continue_statement
    | goto_statement
    | return_statement
    | throw_statement
    ;

Lokalizacja, do której kontrolka przeskoku transferuje instrukcję, jest nazywana elementem docelowym instrukcji skoku.

Gdy instrukcja skoku występuje w bloku, a celem tej instrukcji skoku jest poza tym blokiem, instrukcja jump mówi się, aby zamknąć blok. Podczas gdy instrukcja skoku może przenosić kontrolę z bloku, nigdy nie może przenieść kontroli do bloku.

Wykonywanie instrukcji skoku jest skomplikowane dzięki obecności interweniujących try instrukcji. W przypadku braku takich try stwierdzeń, oświadczenie skoku bezwarunkowo przenosi kontrolę z instrukcji jump do celu. W obecności takich interweniujących try instrukcji wykonywanie jest bardziej złożone. Jeśli instrukcja jump kończy co najmniej jeden try blok ze skojarzonymi finally blokami, kontrolka jest początkowo przenoszona do finally bloku najbardziej wewnętrznej try instrukcji. Gdy kontrolka i jeśli osiągnie punkt finally końcowy bloku, kontrolka zostanie przeniesiona do finally bloku następnej instrukcji otaczającej try . Ten proces jest powtarzany do momentu finally wykonania bloków wszystkich interweniujących try instrukcji.

Przykład: w poniższym kodzie

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");
    }
}

finally bloki skojarzone z dwiema try instrukcjami są wykonywane przed przeniesieniem kontrolki do obiektu docelowego instrukcji jump. Wygenerowane dane wyjściowe są następujące:

Before break
Innermost finally block
Outermost finally block
After break

przykład końcowy

13.10.2 Instrukcja break

Instrukcja break zamyka najbliższą otaczającą switchinstrukcję , while, do, for, lub foreach .

break_statement
    : 'break' ';'
    ;

Elementem docelowym instrukcji break jest punkt końcowy najbliższej otaczającej switchinstrukcji , while, do, for, lub foreach . break Jeśli instrukcja nie jest ujęta w instrukcję switch, while, do, forlubforeach, wystąpi błąd czasu kompilacji.

Gdy wiele switchinstrukcji , , dowhile, forlub jest foreach zagnieżdżonych między sobą, break instrukcja ma zastosowanie tylko do najbardziej wewnętrznej instrukcji. Aby przenieść kontrolę na wiele poziomów zagnieżdżania, goto należy użyć instrukcji (§13.10.4).

Instrukcja break nie może zamknąć finally bloku (§13.11). break Gdy instrukcja występuje w finally bloku, element docelowy instrukcji break mieści się w tym samym finally bloku; w przeciwnym razie wystąpi błąd czasu kompilacji.

Instrukcja break jest wykonywana w następujący sposób:

  • break Jeśli instrukcja kończy co najmniej jeden try blok ze skojarzonymi finally blokami, kontrolka jest początkowo przenoszona do finally bloku najbardziej wewnętrznej try instrukcji. Gdy kontrolka i jeśli osiągnie punkt finally końcowy bloku, kontrolka zostanie przeniesiona do finally bloku następnej instrukcji otaczającej try . Ten proces jest powtarzany do momentu finally wykonania bloków wszystkich interweniujących try instrukcji.
  • Kontrolka jest przenoszona do elementu docelowego instrukcji break .

break Ponieważ oświadczenie bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji break nigdy nie jest osiągalny.

13.10.3 Instrukcja continue

Instrukcja continue rozpoczyna nową iterację najbliższej otaczającej whileinstrukcji , do, forlub foreach .

continue_statement
    : 'continue' ';'
    ;

Elementem docelowym instrukcji continue jest punkt końcowy osadzonej instrukcji najbliższej otaczającej whileinstrukcji , do, forlub foreach . continue Jeśli instrukcja nie jest ujęta w instrukcję while, do, forlubforeach, wystąpi błąd czasu kompilacji.

Gdy wiele whileinstrukcji , do, forlub jest foreach zagnieżdżonych w sobie, continue instrukcja ma zastosowanie tylko do najbardziej wewnętrznej instrukcji. Aby przenieść kontrolę na wiele poziomów zagnieżdżania, goto należy użyć instrukcji (§13.10.4).

Instrukcja continue nie może zamknąć finally bloku (§13.11). continue Gdy instrukcja występuje w finally bloku, element docelowy instrukcji continue mieści się w tym samym finally bloku; w przeciwnym razie wystąpi błąd czasu kompilacji.

Instrukcja continue jest wykonywana w następujący sposób:

  • continue Jeśli instrukcja kończy co najmniej jeden try blok ze skojarzonymi finally blokami, kontrolka jest początkowo przenoszona do finally bloku najbardziej wewnętrznej try instrukcji. Gdy kontrolka i jeśli osiągnie punkt finally końcowy bloku, kontrolka zostanie przeniesiona do finally bloku następnej instrukcji otaczającej try . Ten proces jest powtarzany do momentu finally wykonania bloków wszystkich interweniujących try instrukcji.
  • Kontrolka jest przenoszona do elementu docelowego instrukcji continue .

continue Ponieważ oświadczenie bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji continue nigdy nie jest osiągalny.

13.10.4 Instrukcja goto

Instrukcja goto przenosi kontrolkę do instrukcji oznaczonej etykietą.

goto_statement
    : 'goto' identifier ';'
    | 'goto' 'case' constant_expression ';'
    | 'goto' 'default' ';'
    ;

Elementem docelowym instrukcji goto identyfikatora jest instrukcja oznaczona etykietą z daną etykietą. Jeśli etykieta o podanej nazwie nie istnieje w bieżącym elemencie członkowskim funkcji lub jeśli goto instrukcja nie znajduje się w zakresie etykiety, wystąpi błąd czasu kompilacji.

Uwaga: Ta reguła zezwala na używanie goto instrukcji do przenoszenia kontroli poza zagnieżdżony zakres, ale nie do zagnieżdżonego zakresu. W przykładzie

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}]");
        }
    }
}

instrukcja goto służy do przenoszenia kontroli poza zagnieżdżony zakres.

notatka końcowa

Elementem docelowym instrukcji goto case jest lista instrukcji w natychmiast otaczającej switch instrukcji (§13.8.3), która zawiera etykietę ze stałym wzorcem case podanej stałej wartości i bez ochrony. goto case Jeśli instrukcja nie jest ujęta w switch instrukcję, jeśli najbliższa instrukcja otaczająca switch nie zawiera takiej caseinstrukcji lub jeśli constant_expression nie jest niejawnie konwertowana (§10.2) do typu rządzącego najbliższej otaczającej switch instrukcji, wystąpi błąd czasu kompilacji.

Elementem docelowym instrukcji goto default jest lista instrukcji w natychmiast otaczającej switch instrukcji (§13.8.3), która zawiera etykietę default . goto default Jeśli instrukcja nie jest ujęta w instrukcję switch lub gdy najbliższa instrukcja otaczania switch nie zawiera default etykiety, wystąpi błąd czasu kompilacji.

Instrukcja goto nie może zamknąć finally bloku (§13.11). goto Gdy instrukcja występuje w blokufinally, element docelowy goto instrukcji mieści się w tym samym finally bloku lub w przeciwnym razie wystąpi błąd czasu kompilacji.

Instrukcja goto jest wykonywana w następujący sposób:

  • goto Jeśli instrukcja kończy co najmniej jeden try blok ze skojarzonymi finally blokami, kontrolka jest początkowo przenoszona do finally bloku najbardziej wewnętrznej try instrukcji. Gdy kontrolka i jeśli osiągnie punkt finally końcowy bloku, kontrolka zostanie przeniesiona do finally bloku następnej instrukcji otaczającej try . Ten proces jest powtarzany do momentu finally wykonania bloków wszystkich interweniujących try instrukcji.
  • Kontrolka jest przenoszona do elementu docelowego instrukcji goto .

goto Ponieważ oświadczenie bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji goto nigdy nie jest osiągalny.

13.10.5 Instrukcja return

Instrukcja return zwraca kontrolkę do bieżącego obiektu wywołującego element członkowski funkcji, w którym pojawia się instrukcja return, opcjonalnie zwracając wartość lub variable_reference (§9.5).

return_statement
    : 'return' ';'
    | 'return' expression ';'
    | 'return' 'ref' variable_reference ';'
    ;

Return_statement bez wyrażenia jest nazywana zwracaną wartością; jedno zawierające ref wyrażenie jest nazywane wyrażeniem return-by-ref, a jedno zawierające tylko wyrażenie jest nazywane wyrażeniem return-by-value.

Jest to błąd czasu kompilacji, aby użyć zwracanej wartości z metody zadeklarowanej jako zwracane przez wartość lub zwracane przez ref (§15.6.1).

Jest to błąd czasu kompilacji, aby użyć metody return-by-ref zadeklarowanej jako return-no-value lub return-by-value.

Jest to błąd czasu kompilacji, aby użyć zwracanej przez wartość z metody zadeklarowanej jako zwracana wartość-no-value lub return-by-ref.

Jest to błąd czasu kompilacji, aby użyć wyrażenia return-by-ref, jeśli wyrażenie nie jest variable_reference lub jest odwołaniem do zmiennej, której kontekst ref-safe-context nie jest kontekstem wywołującym (§9.7.2).

Jest to błąd czasu kompilacji, aby użyć metody return-by-ref zadeklarowanej przy użyciu method_modifier async.

Mówi się, że element członkowski funkcji oblicza wartość, jeśli jest metodą metodą zwracaną przez wartość (§15.6.11), metoda zwracana po wartości uzyskuje metodę dostępu do właściwości lub indeksatora lub operatora zdefiniowanego przez użytkownika. Elementy członkowskie funkcji, które są zwracane bez wartości, nie obliczają wartości i są metodami o efektywnym typie voidzwrotnym, ustawiać metody dostępu właściwości i indeksatorów, dodawać i usuwać metody dostępu do zdarzeń, konstruktorów wystąpień, konstruktorów statycznych i finalizatorów. Elementy członkowskie funkcji, które są zwracane przez ref, nie obliczają wartości.

W przypadku zwracanej wartości niejawna konwersja (§10.2) istnieje z typu wyrażenia do efektywnego typu zwracanego typu (§15.6.11) zawierającego elementu członkowskiego funkcji. W przypadku zwrotu po ref konwersja tożsamości (§10.2.2) istnieje między typem wyrażenia a skutecznym typem zwracanym elementu członkowskiego funkcji zawierającej.

returninstrukcje mogą być również używane w treści wyrażeń funkcji anonimowych (§12.19) i uczestniczyć w określaniu, które konwersje istnieją dla tych funkcji (§10.7.1).

Jest to błąd czasu kompilacji instrukcji return wyświetlanej finally w bloku (§13.11).

Instrukcja return jest wykonywana w następujący sposób:

  • W przypadku wyrażenia zwracanego według wartości wyrażenie jest obliczane, a jego wartość jest konwertowana na efektywny typ zwracany funkcji zawierającej przez niejawną konwersję. Wynik konwersji staje się wartością wynikową wygenerowaną przez funkcję. W przypadku wyrażenia return-by-ref wyrażenie jest obliczane, a wynik jest klasyfikowany jako zmienna. Jeśli element return-by-ref metody otaczającej zawiera readonlyzmienną wynikową jest tylko do odczytu.
  • return Jeśli instrukcja jest ujęta przez jeden lub catch więcej try bloków ze skojarzonymi finally blokami, kontrolka jest początkowo przenoszona do finally bloku najbardziej wewnętrznej try instrukcji. Gdy kontrolka i jeśli osiągnie punkt finally końcowy bloku, kontrolka zostanie przeniesiona do finally bloku następnej instrukcji otaczającej try . Ten proces jest powtarzany do momentu finally wykonania bloków wszystkich ujęć try instrukcji.
  • Jeśli funkcja zawierająca nie jest funkcją asynchroniową, kontrolka jest zwracana do obiektu wywołującego funkcji zawierającej wraz z wartością wyniku, jeśli istnieje.
  • Jeśli funkcja zawierająca jest funkcją asynchroniową, kontrolka jest zwracana do bieżącego wywołującego, a wartość wyniku, jeśli istnieje, jest rejestrowana w zadaniu zwrotnym zgodnie z opisem w sekcji (§15.15.3).

return Ponieważ oświadczenie bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji return nigdy nie jest osiągalny.

13.10.6 Instrukcja throw

Instrukcja throw zgłasza wyjątek.

throw_statement
    : 'throw' expression? ';'
    ;

Instrukcja throw z wyrażeniem zgłasza wyjątek wygenerowany przez ocenę wyrażenia. Wyrażenie jest niejawnie konwertowane na System.Exception, a wynik oceny wyrażenia jest konwertowany na System.Exception przed zgłoszeniem. Jeśli wynikiem konwersji jest null, System.NullReferenceException zamiast tego zostanie zgłoszony element .

Instrukcja throw bez wyrażenia może być używana tylko w catch bloku, w takim przypadku instrukcja ta ponownie zgłasza wyjątek, który jest obecnie obsługiwany przez ten catch blok.

throw Ponieważ oświadczenie bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji throw nigdy nie jest osiągalny.

Gdy zgłaszany jest wyjątek, kontrolka jest przekazywana do pierwszej catch klauzuli w otaczającej try instrukcji, która może obsłużyć wyjątek. Proces, który odbywa się od momentu zgłoszenia wyjątku do punktu przenoszenia kontroli do odpowiedniego programu obsługi wyjątków, jest znany jako propagacja wyjątków. Propagacja wyjątku polega na wielokrotnej ocenie poniższych kroków do momentu catch znalezienia klauzuli zgodnej z wyjątkiem. W tym opisie punkt zgłaszania jest początkowo lokalizacją, w której zgłaszany jest wyjątek. To zachowanie jest określone w pliku (§21.4).

  • W bieżącym elemencie członkowskim funkcji każda try instrukcja, która otacza punkt rzutu, jest badana. Dla każdej instrukcji , począwszy od najbardziej wewnętrznej try instrukcji Si kończącej się najbardziej zewnętrzną try instrukcją, oceniane są następujące kroki:

    • try Jeśli blok ujętego w punkt rzutu S i jeśli S ma co najmniej jedną catch klauzulę, catch klauzule są badane w kolejności wyglądu, aby zlokalizować odpowiednią procedurę obsługi dla wyjątku. Pierwsza catch klauzula określająca typ T wyjątku (lub parametr typu, który w czasie wykonywania określa typ wyjątku E ), taki, że typ Tczasu wykonywania pochodzi zT, jest uważany za dopasowanie. Jeśli klauzula zawiera filtr wyjątku, obiekt wyjątku jest przypisywany do zmiennej wyjątku, a filtr wyjątku jest obliczany. Jeśli klauzula catch zawiera filtr wyjątku, ta klauzula jest traktowana jako zgodna, catch jeśli filtr wyjątku truezwróci wartość . Klauzula ogólna catch (§13.11) jest uważana za zgodną z dowolnym typem wyjątku. Jeśli znajduje się klauzula dopasowania catch , propagacja wyjątku jest zakończona przez przeniesienie kontroli do bloku tej catch klauzuli.
    • W przeciwnym razie, jeśli try blok lub catch blok S otacza punkt rzutu i jeśli S ma finally blok, kontrolka jest przenoszona do finally bloku. finally Jeśli blok zgłasza inny wyjątek, przetwarzanie bieżącego wyjątku zostanie zakończone. W przeciwnym razie, gdy kontrolka osiągnie punkt finally końcowy bloku, przetwarzanie bieżącego wyjątku jest kontynuowane.
  • Jeśli program obsługi wyjątków nie znajduje się w wywołaniu bieżącej funkcji, wywołanie funkcji zostanie zakończone i wystąpi jeden z następujących:

    • Jeśli bieżąca funkcja nie jest asynchroniczna, powyższe kroki są powtarzane dla obiektu wywołującego funkcji z punktem throw odpowiadającym instrukcji, z której wywoływano element członkowski funkcji.

    • Jeśli bieżąca funkcja jest asynchronizna i zwracana przez zadanie, wyjątek jest rejestrowany w zadaniu zwrotnym, który jest umieszczany w stanie błędnym lub anulowanym zgodnie z opisem w §15.15.3.

    • Jeśli bieżąca funkcja jest async i void-returning, kontekst synchronizacji bieżącego wątku jest powiadamiany zgodnie z opisem w §15.15.4.

  • Jeśli przetwarzanie wyjątku kończy wszystkie wywołania elementów członkowskich funkcji w bieżącym wątku, wskazując, że wątek nie ma procedury obsługi dla wyjątku, to wątek zostanie zakończony. Wpływ takiego zakończenia jest definiowany przez implementację.

13.11 Instrukcja try

Instrukcja try zawiera mechanizm przechwytywania wyjątków występujących podczas wykonywania bloku. Ponadto instrukcja try zapewnia możliwość określenia bloku kodu, który jest zawsze wykonywany, gdy kontrolka opuszcza instrukcję try .

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
    ;

Try_statement składa się ze słowa kluczowegotry, po którym następuje blok, a następnie zero lub więcej catch_clauses, a następnie opcjonalny finally_clause. Istnieje co najmniej jedna catch_clause lub finally_clause.

W exception_specifier typ lub jego efektywna klasa bazowa, jeśli jest type_parameter, musi być System.Exception lub typ, który pochodzi od niego.

Gdy klauzula catch określa zarówno class_type , jak i identyfikator, zadeklarowana jest zmienna wyjątku podanej nazwy i typu. Zmienna wyjątku jest wprowadzana do przestrzeni deklaracji specific_catch_clause (§7.3). Podczas wykonywania exception_filter i catch bloku zmienna wyjątku reprezentuje obecnie obsługiwany wyjątek. Do celów określonego sprawdzania przypisania zmienna wyjątku jest uznawana za zdecydowanie przypisaną w całym zakresie.

Jeśli klauzula catch nie zawiera nazwy zmiennej wyjątku, nie można uzyskać dostępu do obiektu wyjątku w filtrze i catch bloku.

Klauzula catch określająca ani typ wyjątku, ani nazwę zmiennej wyjątku, jest nazywana klauzulą ogólną catch . Oświadczenie try może mieć tylko jedną klauzulę ogólną catch , a jeśli istnieje, jest to ostatnia catch klauzula.

Uwaga: Niektóre języki programowania mogą obsługiwać wyjątki, które nie są reprezentowane jako obiekt pochodzący z System.Exceptionklasy , chociaż takie wyjątki nigdy nie mogą być generowane przez kod języka C#. Klauzula ogólna catch może służyć do przechwytywania takich wyjątków. W związku z tym klauzula ogólna catch różni się semantycznie od tej, która określa typ System.Exception, w którym były może również przechwytywać wyjątki z innych języków. notatka końcowa

Aby zlokalizować procedurę obsługi dla wyjątku, catch klauzule są badane w kolejności leksykalnej. Jeśli klauzula catch określa typ, ale nie filtr wyjątku, jest to błąd czasu kompilacji dla późniejszej catch klauzuli tej samej try instrukcji, aby określić typ, który jest taki sam, jak lub pochodzi od tego typu.

Uwaga: bez tego ograniczenia możliwe byłoby zapisanie klauzul niemożliwych do catch osiągnięcia. notatka końcowa

catch W bloku throw instrukcja (§13.10.6) bez wyrażenia nie może służyć do ponownego zgłoszenia wyjątku przechwyconego catch przez blok. Przypisania do zmiennej wyjątku nie zmieniają wyjątku, który jest zgłaszany ponownie.

Przykład: w poniższym kodzie

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);
        }
    }
}

metoda F przechwytuje wyjątek, zapisuje pewne informacje diagnostyczne w konsoli, zmienia zmienną wyjątku i ponownie zgłasza wyjątek. Wyjątek, który jest zgłaszany ponownie, jest oryginalnym wyjątkiem, więc generowane dane wyjściowe to:

Exception in F: G
Exception in Main: G

Gdyby pierwszy catch blok został zgłoszony e zamiast ponownie wywrócić bieżący wyjątek, wygenerowane dane wyjściowe będą wyglądać następująco:

Exception in F: G
Exception in Main: F

przykład końcowy

Jest to błąd czasu kompilacji dla breakinstrukcji , continuelub goto w celu przeniesienia kontroli z finally bloku. breakGdy instrukcja , continuelub goto występuje w finally bloku, element docelowy instrukcji mieści się w tym samym finally bloku lub w przeciwnym razie wystąpi błąd czasu kompilacji.

Jest to błąd czasu kompilacji dla return instrukcji, która ma wystąpić w finally bloku.

Gdy wykonanie osiągnie instrukcję, kontrolka try zostanie przeniesiona try do bloku. Jeśli kontrolka osiągnie punkt try końcowy bloku bez propagacji wyjątku, kontrolka zostanie przeniesiona do finally bloku, jeśli istnieje. Jeśli żaden blok nie finally istnieje, kontrolka zostanie przeniesiona do punktu końcowego instrukcji try .

Jeśli rozpropagowano wyjątek, klauzule, catch jeśli istnieją, są badane w kolejności leksykalnej, szukając pierwszego dopasowania dla wyjątku. Wyszukiwanie klauzuli dopasowania catch jest kontynuowane ze wszystkimi otaczającymi blokami zgodnie z opisem w §13.10.6. Klauzula catch jest zgodna, jeśli typ wyjątku pasuje do dowolnego exception_specifier , a wszystkie exception_filter są prawdziwe. Klauzula catch bez exception_specifier pasuje do dowolnego typu wyjątku. Typ wyjątku jest zgodny z exception_specifier, gdy exception_specifier określa typ wyjątku lub podstawowy typ wyjątku. Jeśli klauzula zawiera filtr wyjątku, obiekt wyjątku jest przypisywany do zmiennej wyjątku, a filtr wyjątku jest obliczany.

Jeśli został rozpropagowany wyjątek i zostanie znaleziona zgodna catch klauzula, kontrolka zostanie przeniesiona do pierwszego pasującego catch bloku. Jeśli kontrolka osiągnie punkt catch końcowy bloku bez propagacji wyjątku, kontrolka zostanie przeniesiona do finally bloku, jeśli istnieje. Jeśli żaden blok nie finally istnieje, kontrolka zostanie przeniesiona do punktu końcowego instrukcji try . Jeśli wyjątek został rozpropagowany z catch bloku, kontrolka przenosi do finally bloku, jeśli istnieje. Wyjątek jest propagowany do następnej instrukcji otaczającej try .

Jeśli został rozpropagowany wyjątek i nie zostanie znaleziona żadna zgodna catch klauzula, kontrolka zostanie przeniesiona do finally bloku, jeśli istnieje. Wyjątek jest propagowany do następnej instrukcji otaczającej try .

Instrukcje finally bloku są zawsze wykonywane, gdy kontrolka opuszcza instrukcję try . Jest to prawdą, czy transfer kontrolny występuje w wyniku normalnego wykonywania, w wyniku wykonania breakinstrukcji , continue, goto, lub return w wyniku propagacji wyjątku z instrukcji try . Jeśli kontrolka osiągnie punkt finally końcowy bloku bez propagacji wyjątku, kontrolka zostanie przeniesiona do punktu końcowego instrukcji try .

Jeśli wyjątek jest zgłaszany podczas wykonywania finally bloku i nie zostanie przechwycony w obrębie tego samego finally bloku, wyjątek jest propagowany do następnej instrukcji otaczającej try . Jeśli inny wyjątek był w trakcie propagacji, ten wyjątek zostanie utracony. Proces propagowania wyjątku został omówiony bardziej szczegółowo w opisie throw instrukcji (§13.10.6).

Przykład: w poniższym kodzie

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");
        }
    }
}

metoda Method zgłasza wyjątek. Pierwszą akcją jest sprawdzenie ujęć catch klauzul, wykonując filtry wyjątków. Następnie klauzula finally w Method pliku jest wykonywana przed przeniesieniem kontrolki do otaczającej klauzuli dopasowania catch . Wynikowe dane wyjściowe to:

Filter
Finally
Catch

przykład końcowy

Blok try instrukcji try jest osiągalny, jeśli try instrukcja jest osiągalna.

Blok catch instrukcji try jest osiągalny, jeśli try instrukcja jest osiągalna.

Blok finally instrukcji try jest osiągalny, jeśli try instrukcja jest osiągalna.

Punkt końcowy instrukcji try jest osiągalny, jeśli oba z następujących stwierdzeń są prawdziwe:

  • Punkt try końcowy bloku jest osiągalny lub punkt końcowy co najmniej jednego catch bloku jest osiągalny.
  • finally Jeśli blok jest obecny, punkt finally końcowy bloku jest osiągalny.

13.12 Zaznaczone i niezaznaczone instrukcje

Instrukcje checked i unchecked służą do kontrolowania kontekstu sprawdzania przepełnienia dla operacji arytmetycznych i konwersji typu całkowitego.

checked_statement
    : 'checked' block
    ;

unchecked_statement
    : 'unchecked' block
    ;

Instrukcja checked powoduje, że wszystkie wyrażenia w bloku mają być oceniane w kontekście sprawdzonym, a unchecked instrukcja powoduje, że wszystkie wyrażenia w bloku mają być oceniane w nieznakowanym kontekście.

Instrukcje checked i unchecked są dokładnie równoważne checked operatorom i unchecked (§12.8.20), z tą różnicą, że działają na blokach zamiast wyrażeń.

13.13 Instrukcja lock

Instrukcja lock uzyskuje blokadę wzajemnego wykluczania dla danego obiektu, wykonuje instrukcję, a następnie zwalnia blokadę.

lock_statement
    : 'lock' '(' expression ')' embedded_statement
    ;

Wyrażenie instrukcji lock oznacza wartość typu znanego jako odwołanie. Nie jest wykonywana niejawna konwersja boksu (§10.2.9) dla wyrażenia lock instrukcji, a zatem jest to błąd czasu kompilacji dla wyrażenia, aby oznaczyć wartość value_type.

Instrukcja lock formularza

lock (x)

gdzie x jest wyrażeniem reference_type, jest dokładnie równoważne:

bool __lockWasTaken = false;
try
{
    System.Threading.Monitor.Enter(x, ref __lockWasTaken);
    ...
}
finally
{
    if (__lockWasTaken)
    {
        System.Threading.Monitor.Exit(x);
    }
}

z wyjątkiem tego, że x jest obliczany tylko raz.

Chociaż blokada wzajemnego wykluczania jest przechowywana, kod wykonywany w tym samym wątku wykonywania może również uzyskać i zwolnić blokadę. Jednak kod wykonywany w innych wątkach nie może uzyskać blokady do momentu zwolnienia blokady.

13.14 Instrukcja using

Instrukcja using uzyskuje co najmniej jeden zasób, wykonuje instrukcję, a następnie usuwa zasób.

using_statement
    : 'using' '(' resource_acquisition ')' embedded_statement
    ;

resource_acquisition
    : local_variable_declaration
    | expression
    ;

Zasób to klasa lub struktura, która implementuje System.IDisposable interfejs, który zawiera pojedynczą metodę bez parametrów o nazwie .Dispose Kod używający zasobu może wywołać Dispose polecenie , aby wskazać, że zasób nie jest już potrzebny.

Jeśli forma resource_acquisition jest local_variable_declaration, typ local_variable_declaration musi być albo dynamic typ, który można niejawnie przekonwertować na System.IDisposable. Jeśli forma resource_acquisition jest wyrażeniem, wyrażenie to jest niejawnie konwertowane na System.IDisposable.

Zmienne lokalne zadeklarowane w resource_acquisition są tylko do odczytu i zawierają inicjator. Błąd czasu kompilacji występuje, jeśli osadzona instrukcja próbuje zmodyfikować te zmienne lokalne (za pomocą przypisania lub ++ operatorów i -- ), podjąć adresy lub przekazać je jako parametry referencyjne lub wyjściowe.

Instrukcja using jest tłumaczona na trzy części: pozyskiwanie, użycie i usuwanie. Użycie zasobu jest niejawnie ujęte w instrukcję try zawierającą klauzulę finally . Ta finally klauzula usuwa zasób. null Jeśli zasób zostanie pozyskany, nie zostanie wykonane żadne wywołanie Dispose i nie zostanie zgłoszony żaden wyjątek. Jeśli zasób jest typu dynamic , jest on dynamicznie konwertowany przez niejawną konwersję dynamiczną (§10.2.10) na IDisposable podczas pozyskiwania w celu zapewnienia pomyślnej konwersji przed użyciem i likwidacją.

Instrukcja using formularza

using (ResourceType resource = «expression» ) «statement»

odpowiada jednemu z trzech możliwych rozszerzeń. Jeśli ResourceType jest typem wartości innej niż null lub parametrem typu z ograniczeniem typu wartości (§15.2.5), rozszerzenie jest semantycznie równoważne

{
    ResourceType resource = «expression»;
    try
    {
        «statement»;
    }
    finally
    {
        ((IDisposable)resource).Dispose();
    }
}

z wyjątkiem tego, że rzut do resource System.IDisposable nie powoduje wystąpienia boksu.

W przeciwnym razie, gdy ResourceType ma dynamicwartość , rozszerzenie jest

{
    ResourceType resource = «expression»;
    IDisposable d = resource;
    try
    {
        «statement»;
    }
    finally
    {
        if (d != null)
        {
            d.Dispose();
        }
    }
}

W przeciwnym razie rozszerzenie jest

{
    ResourceType resource = «expression»;
    try
    {
        «statement»;
    }
    finally
    {
        IDisposable d = (IDisposable)resource;
        if (d != null)
        {
            d.Dispose();
        }
    }
}

W każdym rozszerzeniu zmienna resource jest tylko do odczytu w instrukcji osadzonej, a d zmienna jest niedostępna i niewidoczna dla osadzonej instrukcji.

Implementacja może implementować daną using_statement inaczej, np. ze względów wydajności, o ile zachowanie jest zgodne z powyższym rozszerzeniem.

Instrukcja using formularza:

using («expression») «statement»

ma te same trzy możliwe rozszerzenia. W tym przypadku ResourceType jest niejawnie typ czasu kompilacji wyrażenia, jeśli ma taki typ. W przeciwnym razie sam interfejs IDisposable jest używany jako ResourceType. Zmienna resource jest niedostępna i niewidoczna dla osadzonej instrukcji.

Gdy resource_acquisition ma formę local_variable_declaration, można uzyskać wiele zasobów danego typu. Instrukcja using formularza

using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) «statement»

jest dokładnie odpowiednikiem sekwencji zagnieżdżonych using instrukcji:

using (ResourceType r1 = e1)
using (ResourceType r2 = e2)
...
using (ResourceType rN = eN)
«statement»

Przykład: Poniższy przykład tworzy plik o nazwie log.txt i zapisuje dwa wiersze tekstu w pliku. Następnie przykład otwiera ten sam plik do odczytu i kopiuje zawarte wiersze tekstu do konsoli.

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);
            }
        }
    }
}

TextWriter Ponieważ klasy i TextReader implementują IDisposable interfejs, przykład może użyć using instrukcji, aby upewnić się, że plik źródłowy jest poprawnie zamknięty po operacjach zapisu lub odczytu.

przykład końcowy

13.15 Instrukcja wydajności

Instrukcja yield jest używana w bloku iteratora (§13.3), aby uzyskać wartość obiektu wyliczającego (§15.14.5) lub obiekt wyliczalny (§15.14.6) iteratora lub sygnalizować koniec iteracji.

yield_statement
    : 'yield' 'return' expression ';'
    | 'yield' 'break' ';'
    ;

yield jest kontekstowym słowem kluczowym (§6.4.4) i ma specjalne znaczenie tylko wtedy, gdy jest używane bezpośrednio przed return słowem kluczowym lub break .

Istnieje kilka ograniczeń dotyczących tego, gdzie można wyświetlić instrukcję yield , zgodnie z opisem w poniższym artykule.

  • Jest to błąd czasu kompilacji instrukcji yield (formularza) wyświetlanej poza method_body, operator_body lub accessor_body.
  • Jest to błąd czasu kompilacji instrukcji yield (z dowolnego formularza) wyświetlanej wewnątrz funkcji anonimowej.
  • Jest to błąd czasu kompilacji instrukcji yield (z dowolnego formularza), która ma być wyświetlana w finally klauzuli instrukcji try .
  • Jest to błąd czasu kompilacji instrukcji yield return , która ma być wyświetlana w dowolnym miejscu w try instrukcji zawierającej dowolne catch_clauses.

Przykład: W poniższym przykładzie pokazano prawidłowe i nieprawidłowe zastosowania instrukcji yield .

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
}

przykład końcowy

Niejawna konwersja (§10.2) istnieje z typu wyrażenia w yield return instrukcji do typu rentowności (§15.14.4) iteratora.

Instrukcja yield return jest wykonywana w następujący sposób:

  • Wyrażenie podane w instrukcji jest obliczane, niejawnie konwertowane na typ plonu i przypisywane do Current właściwości obiektu wyliczającego.
  • Wykonanie bloku iteratora jest zawieszone. yield return Jeśli instrukcja znajduje się w co najmniej jednym try bloku, skojarzone finally bloki nieobecnie wykonywane.
  • MoveNext Metoda obiektu modułu wyliczającego powraca true do obiektu wywołującego, co wskazuje, że obiekt modułu wyliczającego pomyślnie został zaawansowany do następnego elementu.

Następne wywołanie metody obiektu MoveNext modułu wyliczającego wznawia wykonywanie bloku iteratora z miejsca ostatniego wstrzymania.

Instrukcja yield break jest wykonywana w następujący sposób:

  • yield break Jeśli instrukcja jest ujęta przez co najmniej jeden try blok ze skojarzonymi finally blokami, kontrolka jest początkowo przenoszona do finally bloku najbardziej wewnętrznej try instrukcji. Gdy kontrolka i jeśli osiągnie punkt finally końcowy bloku, kontrolka zostanie przeniesiona do finally bloku następnej instrukcji otaczającej try . Ten proces jest powtarzany do momentu finally wykonania bloków wszystkich ujęć try instrukcji.
  • Kontrolka jest zwracana do obiektu wywołującego blok iteratora. Jest MoveNext to metoda lub Dispose metoda obiektu modułu wyliczającego.

yield break Ponieważ oświadczenie bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji yield break nigdy nie jest osiągalny.