Udostępnij za pośrednictwem


13 oświadczeń

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 dla instrukcji, które pojawiają się w innych instrukcjach. Użycie instrukcji embedded_statement zamiast instrukcji wyklucza użycie instrukcji deklaracyjnych i oznaczonych etykietami w tych kontekstach.

Przykład: kod

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

powoduje błąd w czasie kompilacji, ponieważ if instrukcja wymaga embedded_statement, zamiast 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 deklaracji i 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 zdania to lokalizacja, która następuje natychmiast po zdaniu. 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 jest nieosiągalne, ponieważ nie można wykonać tej instrukcji.

przykład końcowy

Zostanie zgłoszone ostrzeżenie, jeśli instrukcja inna niż throw_statement, block lub empty_statement jest nieosiągalna. Nie jest to szczególnie błędem, gdy oświadczenie jest nieosiągalne.

Uwaga: Aby określić, czy określona instrukcja lub punkt końcowy jest osiągalny, 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 nieustalone danego typu jest uznawane za mogące mieć dowolną możliwą wartość 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 i generuje wartość false, wywołanie Console.WriteLine jest uznawane za nieosiągalne. Jeśli i jednak zostanie zmieniona na zmienną lokalną

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

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

notatka końcowa

Blok członka 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ść drugiego 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:

  • Ponieważ instrukcja switch nie zezwala na „przejście” sekcji przełącznika do następnej sekcji przełącznika, stanowi to błąd czasu kompilacji, jeśli punkt końcowy listy instrukcji sekcji przełącznika jest osiągalny. Jeśli wystąpi ten błąd, zazwyczaj oznacza to, że brakuje instrukcji break.

  • 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 brakuje instrukcji return(§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 uznawany jest za pusty.

Blok może zawierać instrukcje 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, sterowanie zostaje przeniesione do listy instrukcji. Gdy i jeśli przepływ sterowania osiągnie punkt końcowy listy instrukcji, zostanie on przeniesiony 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.15). Niektóre dodatkowe ograniczenia dotyczą bloków iteratora:

  • Błąd czasu kompilacji występuje, gdy instrukcja return pojawia się w bloku iteratora (ale instrukcje yield return 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_block (§13.8.3).

statement_list
    : statement+
    ;

Lista instrukcji jest wykonywana przez przeniesienie kontrolki do pierwszej instrukcji. Gdy i jeśli sterowanie dotrze do końca instrukcji, zostanie ono przeniesione do następnej instrukcji. Kiedy i jeśli kontrola dotrze do punktu końcowego ostatniej instrukcji, zostanie przeniesiona do punktu końcowego listy instrukcji.

Instrukcja na liście wyrażeń jest osiągalna, jeśli co najmniej jeden z następujących warunków jest spełniony:

  • Oświadczenie jest pierwszym oświadczeniem, a sama lista oświadczeń jest osiągalna.
  • Punkt końcowy poprzedniej instrukcji jest osiągalny.
  • Instrukcja jest oznaczona etykietą, a etykieta jest przywoływana 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 oznaczonym jako „}”.

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

przykład końcowy

13.5 Instrukcje oznaczone etykietą

Labeled_statement pozwala na umieszczenie etykiety przed instrukcją. 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 obejmuje cały blok, w którym etykieta jest zadeklarowana, łącznie z zagnieżdżonymi blokami. Jest to błąd czasu kompilacji, gdy dwie etykiety o tej samej nazwie mają 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 na zewnątrz 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żeli etykieta jest przywoływana przez osiągalną instrukcję goto, chyba że instrukcja goto znajduje się wewnątrz bloku try lub bloku catch w ramach try_statement, który zawiera blok finally mający nieosiągalny punkt końcowy, a instrukcja oznaczona etykietą znajduje się poza try_statement.

Deklaracje 13.6

13.6.1 Ogólne

Declaration_statement deklaruje co najmniej jedną zmienną lokalną, co najmniej jedną stałą lokalną lub funkcję lokalną. Deklaracje są dozwolone w blokach i blokach switch, ale nie są dozwolone jako instrukcje wbudowane.

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 deklaracyjnej (§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 w zakresie nie ma typu o nazwie var, a dane wejściowe są zgodne z implicitly_typed_local_variable_declaration, to zostanie ono wybrane.
  • W przeciwnym razie, jeśli typ o nazwie var znajduje się w zakresie, to implicitly_typed_local_variable_declaration nie jest traktowane jako 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, gdy local_variable_declaration nie występuje jako for_initializer (§13.9.4) lub resource_acquisition (§13.14), porządek z lewej do prawej jest równoważny z umieszczeniem każdego deklaratora w osobnym local_variable_declaration. 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_declarationjest 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 występuje błąd czasu kompilacji, gdy zadeklaruje się inną zmienną lokalną, funkcję lokalną lub stałą o tej samej nazwie.

Kontekst ref-safe zmiennej lokalnej ref (§9.7.2) jest taki sam jak kontekst ref-safe odniesienia variable_reference podczas inicjalizacji. 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
    ;

Deklaracja zmiennej lokalnej o typie niejawnie określonym wprowadza pojedynczą zmienną lokalną, identyfikator. Wyrażenie lub variable_reference mają typ czasu kompilacji. T Pierwsza alternatywa deklaruje zmienną z początkową wartością wyrażenia; jego typ jest T? , gdy T jest typem referencyjnym bez wartości null, w przeciwnym razie jego typem jest T. Druga alternatywa deklaruje zmienną ref z początkową wartością refvariable_reference; jego typem jest ref T? gdy T jest typem referencyjnym niemającym 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 musi mieć typ i spełniać te same wymagania co w przypadku przypisania ref (§12.21.3).

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

Jest to błąd czasu kompilacji, jeśli zadeklarowana zostanie zmienna lokalna ref lub zmienna pewnego rodzaju ref struct w metodzie zadeklarowanej z modyfikatorem metodyasync, lub w iteratorze (§15.15).

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
    ;

Typlocal_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 deklaracji stałej lokalnej są zgodne z tymi samymi zasadami co stałej deklaracji 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 gramatyki: W przypadku rozpoznawania local_function_body , jeśli mają zastosowanie 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.

Identyfikatorlocal_function_declaration musi być unikatowy w zadeklarowanym zakresie bloku, 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.14) i jeden unsafe modyfikator (§23.1). Jeżeli deklaracja zawiera async modyfikator, typ zwracany musi być void lub «TaskType» typ (§15.14.1). Jeśli deklaracja zawiera static modyfikator, funkcja jest statyczną funkcją lokalną; w przeciwnym razie jest to funkcja niestatyczna lokalna. Występuje błąd czasu kompilacji, gdy 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 otaczających zmiennych lokalnych, parametrów, niestatycznych funkcji lokalnych lub this). Jest to błąd czasu kompilacji, jeśli przechwycona zmienna jest odczytywana w ciele niestatycznej funkcji lokalnej, ale nie jest jednoznacznie przypisana przed każdym wywołaniem funkcji. Kompilator określa, które zmienne są zdecydowanie przypisywane po powrocie (§9.4.4.33).

Gdy typ this jest typem struktury, występuje błąd czasu kompilacji, gdy treść funkcji lokalnej próbuje uzyskać dostęp do this. To prawda, niezależnie od tego, czy dostęp jest wyraźny (jak w this.x), czy niejawny (jak w x, gdzie x jest elementem instancji struktury). Ta reguła jedynie zabrania takiego dostępu i nie wpływa na to, czy wyszukiwanie członków zwraca element członkowski struktury.

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

Uwaga: powyższe reguły dla this i goto odzwierciedlają zasady dotyczące funkcji anonimowych w §12.19.3. notatka końcowa

Funkcja lokalna może być wywoływana z punktu leksyktycznego przed jego deklaracją. Jednak zadeklarowanie funkcji leksykalnie przed deklaracją zmiennej używanej w funkcji lokalnej stanowi błąd czasu kompilacji (§7.7).

Błędem w czasie kompilacji jest, jeżeli funkcja lokalna zadeklaruje parametr, parametr typu lub zmienną lokalną o tej samej nazwie co już zadeklarowana w dowolnej wewnętrznej przestrzeni deklaracji zmiennych lokalnych.

Ciała lokalnych 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 jest osiągalna, mimo że punkt początkowy L nie jest osiągalny. Ponieważ początek L nie jest dostępny, instrukcja po końcu L nie jest dostępna:

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 można odwoływać się do this, base ani do elementów członkowskich instancji z niejawnego odwołania this, ani do zmiennych lokalnych, parametrów, ani funkcji lokalnych niezwiązanych ze statyką 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 zdania.

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 sterowanie 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: Instrukcja w formie if

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:

  • Podlega ocenie boolean_expression (§12.24).
  • Jeśli wyrażenie logiczne zwróci wartość true, sterowanie zostanie przekazane 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 nie ma części else, sterowanie przekazywane jest do punktu końcowego instrukcji if.

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

Jeśli jest obecna, druga osadzona instrukcja if jest osiągalna, jeżeli instrukcja if jest osiągalna i 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 else jest osiągalny, jeśli instrukcja if 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ęcej switch_sections, ujętego 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 przypadek został uznany za spełniony.

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

  • Jeśli typem wyrażenia przełącznika jest sbyte, byte, short, ushort, int, uint, long, ulong, char, bool, string lub enum_type, lub jeśli jest to typ wartości dopuszczający wartość null odpowiadający jednemu z tych typów, to jest typ zarządzający instrukcji switch.
  • 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ądzających: sbyte, byte, short, ushort, int, uint, long, ulong, char, string, lub typu wartości dopuszczającej wartość null odpowiadającego jednemu z tych typów, to przekonwertowany typ staje się typem zarządzającym instrukcją switch.
  • 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 default może znajdować się co najwyżej jedna switch etykieta.

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 zawarty przez (§11.3) zestaw wzorców wcześniejszych etykiet przełącznika w instrukcji switch, które nie mają gardy przypadków lub której garda przypadków 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 switch jest obliczane i konwertowane na typ sterują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 default jest obecna, kontrola zostanie przekazana 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 musi określić leksykalnie pierwszy wzorzec, który dopasowuje się do wyrażenia i dla którego klauzula strażnika jest nieobecna lub ocenia się na 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ęzyków C i C++, wykonanie sekcji switch nie może przejść do kolejnej sekcji switch, a przykład

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

powoduje wystąpienie błędu czasu kompilacji. Jeśli wykonanie sekcji przełącznika ma być kontynuowane przez inną sekcję, należy użyć jawnej instrukcji goto case 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 case lub goto default, ale każda konstrukcja, która czyni punkt końcowy listy instrukcji niedostępnym, jest dozwolona. Na przykład, instrukcja while kontrolowana przez wyrażenie logiczne true jest znane, że nigdy nie osiąga punktu końcowego. Podobnie, instrukcja throw lub return zawsze przekazuje sterowanie 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 switchstring. 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 switch jest rozróżniająca wielkość liter i wykona daną sekcję tylko wtedy, gdy wyrażenie w instrukcji switch dokładnie pasuje do stałej etykiety case. notatka końcowa Gdy typ zarządzający instrukcji switch jest string lub typem dopuszczającym wartość null, wartość null jest dozwolone jako stała etykiety.

Listy_instrukcji switch_block mogą zawierać instrukcje 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 case, którego wzorzec pasowałby (§11.2.1) do tej wartości, a ochrona etykiety jest albo nieobecna, albo nie jest stałym wyrażeniem o wartości false; lub
    • jest to etykieta default, a żadna sekcja przełącznika nie zawiera etykiety case, której wzorzec będzie pasował do tej wartości i której warunek jest nieobecny lub stałe wyrażenie o wartości true.
  • Wyrażenie przełącznika nie jest wartością stałą ani
    • 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 instrukcją switch jest typem dopuszczającym wartość null, a zestaw wzorców wśród przypadków instrukcji switch, które nie mają warunków lub mają warunki o stałej wartości true, nie zawiera wzorca pasującego do wartości null.
  • Etykieta przełącznika jest referencją dla osiągalnej instrukcji goto case lub goto default.

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 .
  • Nie ma etykiety default, a także
    • Wyrażenie przełącznika jest wartością niebędącą stałą, a zestaw wzorców pojawiających się wśród przypadków instrukcji switch, które nie mają osłon lub mają osłony, których wartość jest stałą o wartości true, nie jest wyczerpujący (§11.4) dla typu zarządzającego przełącznikiem.
    • Wyrażenie przełącznika jest wartością niestałą typu dopuszczającego null, a żaden wzorzec wśród przypadków instrukcji switch, które nie posiadają strażników albo mają strażników, których wartość jest stałą true, nie będzie zgodny z wartością null.
    • Wyrażenie przełącznika jest stałą wartością i żadna case etykieta bez warunku lub z warunkiem będącym stałą true nie byłaby zgodna z tą 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";
    }
}

Konstrukcja var dopasowuje się do null, ciągu pustego albo dowolnego ciągu zawierającego tylko białe znaki. przykład końcowy

13.9 Instrukcje iteracji

13.9.1 Ogólne

Instrukcje iteracji wykonują osadzoną instrukcję wielokrotnie.

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:

  • Podlega ocenie boolean_expression (§12.24).
  • Jeśli wyrażenie logiczne zwróci wartość true, kontrola zostanie przeniesiona do instrukcji osadzonej. Jeśli sterowanie dotrze do punktu końcowego osadzonej instrukcji na skutek wykonania instrukcji continue, zostaje ono przeniesione na początek instrukcji while.
  • Jeśli wyrażenie logiczne daje wartość false, sterowanie jest przenoszone do końca instrukcji while.

W osadzonej instrukcji while, instrukcja break (§13.10.2) może służyć do przenoszenia kontroli do punktu końcowego instrukcji while (kończąc w ten sposób iterację osadzonej instrukcji), natomiast instrukcja continue (§13.10.3) może być użyta do przenoszenia kontroli do punktu końcowego osadzonej instrukcji (w ten sposób wykonując kolejną iterację instrukcji while).

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:

  • Kontrola jest przenoszona do osadzonej instrukcji.
  • Gdy i jeśli sterowanie osiągnie punkt końcowy osadzonej instrukcji (możliwe z wykonania instrukcji continue), boolean_expression (§12.24) jest oceniany. Jeśli wyrażenie logiczne zwraca wartość true, kontrola jest przenoszona na początek instrukcji do. W przeciwnym razie kontrolka jest przenoszona do punktu końcowego instrukcji do .

W osadzonej instrukcji do, instrukcja break (§13.10.2) może służyć do przenoszenia kontroli do punktu końcowego instrukcji do (kończąc w ten sposób iterację osadzonej instrukcji), natomiast instrukcja continue (§13.10.3) może być użyta do przenoszenia kontroli do punktu końcowego osadzonej instrukcji (w ten sposób wykonując kolejną iterację instrukcji do).

Osadzona instrukcja do jest osiągalna, jeśli instrukcja do 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_expressions (§13.7) oddzielonych przecinkami. Zakres zmiennej lokalnej deklarowanej w for_initializer obejmuje for_initializer, for_condition, for_iterator oraz embedded_statement.

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

for_iterator, jeśli istnieje, składa się z listy statement_expression (§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, w której są zapisywane. Ten krok jest wykonywany tylko raz.
  • Jeśli for_condition jest obecny, zostanie on oceniony.
  • Jeśli for_condition nie istnieje lub jeśli ocena zwraca wartość true, kontrolka zostanie przeniesiona do instrukcji osadzonej. Kiedy i jeśli sterowanie osiągnie punkt końcowy osadzonej instrukcji (na skutek wykonania instrukcji continue), wyrażenia for_iterator, jeśli istnieją, są ewaluowane w sekwencji, a następnie wykonywana jest kolejna iteracja, począwszy od oceny for_condition w opisanym powyżej kroku.
  • Jeśli for_condition jest obecny, a ocena daje wartość false, kontrola jest przekazywana do punktu końcowego for instrukcji.

W osadzonej instrukcji for, instrukcja break (§13.10.2) może służyć do przenoszenia kontroli do punktu końcowego instrukcji for (tym samym kończąc iterację osadzonej instrukcji), a instrukcja continue (§13.10.3) może służyć do przenoszenia kontroli do punktu końcowego osadzonej instrukcji (wykonując tym samym for_iterator i przeprowadzając kolejną iterację instrukcji for, zaczynając od for_condition).

Osadzone wyrażenie for jest osiągalne, jeśli jedno z następujących stwierdzeń jest prawdziwe:

  • Instrukcja for jest osiągalna, a for_condition nie występuje.
  • 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

13.9.5.1 Ogólne

Instrukcja foreach enumeruje elementy kolekcji i wykonuje wbudowaną instrukcję dla każdego elementu z tej kolekcji.

foreach_statement
    : 'await'? '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 typowana zmienna iteracyjna, a jej typ jest uznawany za typ elementu instrukcji foreach, jak określono poniżej.

Jest to błąd czasu kompilacji, gdy zarówno await jak i ref_kind są obecne w elemencie foreach statement.

Jeśli foreach_statement zawiera zarówno ref i readonly, jak i żadnego z nich, zmienna 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 o zasięgu obejmującym instrukcję osadzoną. Podczas wykonywania instrukcji foreach zmienna iteracji reprezentuje element kolekcji, dla którego jest obecnie wykonywana iteracja. Jeśli zmienna iteracji oznacza niemodyfikowalną zmienną, pojawi się błąd kompilacji, jeśli osadzona instrukcja próbuje ją zmodyfikować (za pośrednictwem przypisania lub operatorów ++ i --) lub przekazać ją jako odwołanie lub parametr wyjściowy.

Przetwarzanie w czasie kompilacji instrukcji foreach najpierw określa typ kolekcji, typ modułu wyliczającego i typ iteracji wyrażenia. Przetwarzanie oświadczenia foreach zostało szczegółowo opisane w § 13.9.5.2 , a proces dla oświadczenia await foreach został opisany w §13.9.5.3.

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

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

13.9.5.2 Synchroniczne foreach

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

  • Jeśli X typu wyrażenia jest typem tablicy, to istnieje niejawna konwersja odwołania z X na interfejs IEnumerable (ponieważ System.Array implementuje ten interfejs). Typ kolekcji to interfejs IEnumerable, typem modułu wyliczającego to interfejs IEnumerator, a typ iteracji to typ elementu tablicy X.
  • Jeśli typ X wyrażenia jest dynamic, istnieje niejawna konwersja z wyrażenia do interfejsu IEnumerable (§10.2.10). Typ kolekcji jest interfejsem IEnumerable, a typ wyliczającego jest interfejsem IEnumerator. var Jeśli identyfikator jest podany jako local_variable_type, to typ iteracji jest dynamic, w przeciwnym razie jest object.
  • W przeciwnym razie określ, czy typ X ma odpowiednią metodę GetEnumerator:
    • Przeprowadź wyszukiwanie członków dla typu X z identyfikatorem GetEnumerator i bez argumentów typu. Jeśli wyszukiwanie członka nie daje dopasowania, powoduje niejednoznaczność lub zwraca dopasowanie, które nie jest zbiorem metod, sprawdź, czy istnieje interfejs wyliczalny zgodnie z poniższym opisem. Zaleca się, aby ostrzeżenie zostało wydane, jeśli wyszukiwanie elementu członkowskiego generuje cokolwiek oprócz 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 produkuje coś innego niż jednoznaczną metodę instancji publicznej lub brak odpowiednich metod.
    • Jeśli zwracany typ E metody GetEnumerator nie jest klasą, strukturą ani typem interfejsu, zostanie wygenerowany błąd i nie zostaną wykonane żadne dalsze kroki.
    • Wyszukiwanie członków jest wykonywane na E bez argumentów typu z identyfikatorem Current. 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 członków jest wykonywane na E bez argumentów typu z identyfikatorem MoveNext. Jeśli wyszukiwanie członka nie daje dopasowania 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 prowadzi do żadnej odpowiedniej metody, skutkuje niejednoznacznością lub wyłania się jedna najlepsza metoda, ale ta metoda jest statyczna, nie jest publiczna lub jej typ zwracany jest różny od bool, to generowany jest błąd i nie są podejmowane dalsze kroki.
    • Typ kolekcji to X, typ modułu wyliczającego jest E, a typ iteracji jest typem właściwości Current. Właściwość Current może zawierać ref modyfikator, w takim przypadku zwrócone wyrażenie jest variable_reference (§9.5), który jest opcjonalnie tylko do odczytu.
  • W przeciwnym razie sprawdź interfejs enumerowalny:
    • Jeśli wśród wszystkich typów Tᵢ, dla których istnieje niejawna konwersja z X do IEnumerable<Tᵢ>, istnieje unikatowy typ T, taki że T nie jest dynamic i dla wszystkich pozostałych Tᵢ istnieje niejawna konwersja z IEnumerable<T> do IEnumerable<Tᵢ>, to typ kolekcji jest interfejsem IEnumerable<T>, typ wyliczający jest interfejsem 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ą podjęte żadne dalsze kroki.
    • W przeciwnym razie, jeśli istnieje niejawna konwersja z X do interfejsu System.Collections.IEnumerable, wtedy typ kolekcji jest tym interfejsem, typ enumeratora to interfejs System.Collections.IEnumerator, a typ iteracji to object.
    • 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 C, typ modułu wyliczającego E i typ iteracji T, ref T lub ref readonly T. Instrukcja foreach formularza

foreach (V v in x) «embedded_statement»

jest wówczas równoważny z:

{
    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»

jest wówczas równoważny z:

{
    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 referencyjna v jest do odczytu i zapisu w osadzonej instrukcji, ale v nie może być ponownie przypisana przy użyciu ref (§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.

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

Umieszczenie v wewnątrz pętli while jest istotne dla sposobu, w jaki jest przechwytywane (§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 rozszerzonej formie został zadeklarowany poza pętlą while, będzie on współdzielony we wszystkich iteracjach, a jego wartość po pętli for będzie wartością końcową, 13, którą wywołanie f wydrukuje. Zamiast tego, ponieważ każda iteracja ma swoją własną zmienną v, ta przechwycona przez f w pierwszej iteracji będzie nadal przechowywać wartość 7, która zostanie wydrukowana. (Należy pamiętać, że wcześniejsze wersje języka C# deklarowały v poza pętlą while.)

przykład końcowy

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

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

    • Jeśli E jest typem wartości niemającym wartości null, klauzula finally rozszerza się 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ściowym lub parametrem typu utworzonego jako typ wartościowy, konwersja e na System.IDisposable nie powoduje pakowania.

  • 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.9.5.3 await foreach

Przetwarzanie w czasie kompilacji instrukcji foreach najpierw określa typ kolekcji, typ modułu wyliczającego i typ iteracji wyrażenia. Przetwarzanie oświadczenia foreach zostało szczegółowo opisane w § 13.9.5.2 , a proces dla oświadczenia await foreach został opisany w §13.9.5.3.

Determinacja przebiega w następujący sposób:

  • Ustal, czy typ X ma odpowiednią GetAsyncEnumerator metodę:
    • Przeprowadź wyszukiwanie członków dla typu X z identyfikatorem GetAsyncEnumerator i bez argumentów typu. Jeśli wyszukiwanie członka nie daje dopasowania, powoduje niejednoznaczność lub zwraca dopasowanie, które nie jest zbiorem metod, sprawdź, czy istnieje interfejs wyliczalny zgodnie z poniższym opisem. Zaleca się, aby ostrzeżenie zostało wydane, jeśli wyszukiwanie elementu członkowskiego generuje cokolwiek oprócz 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 produkuje coś innego niż jednoznaczną metodę instancji publicznej lub brak odpowiednich metod.
    • Jeśli zwracany typ E metody GetAsyncEnumerator nie jest klasą, strukturą ani typem interfejsu, zostanie wygenerowany błąd i nie zostaną wykonane żadne dalsze kroki.
    • Wyszukiwanie członków jest wykonywane na E bez argumentów typu z identyfikatorem Current. 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 członków jest wykonywane na E bez argumentów typu z identyfikatorem MoveNextAsync. Jeśli wyszukiwanie członka nie daje dopasowania 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ążeń nie powoduje żadnych odpowiednich metod, powoduje niejednoznaczność lub powoduje pojedynczą najlepszą metodę, ale ta metoda jest statyczna lub nie jest publiczna lub jej typ zwracany nie jest oczekiwany (§12.9.8.2), gdzie await_expression jest klasyfikowana jako bool (§12.9.8.3), zostanie wygenerowany błąd i nie zostaną wykonane żadne dalsze kroki.
    • Typ kolekcji to X, typ modułu wyliczającego jest E, a typ iteracji jest typem właściwości Current.
  • W przeciwnym razie sprawdź interfejs wyliczalny asynchroniczny:
    • Jeśli wśród wszystkich typów Tᵢ, dla których istnieje niejawna konwersja z X do IAsyncEnumerable<Tᵢ>, istnieje unikatowy typ T, taki że T nie jest dynamic i dla wszystkich pozostałych Tᵢ istnieje niejawna konwersja z IAsyncEnumerable<T> do IAsyncEnumerable<Tᵢ>, to typ kolekcji jest interfejsem IAsyncEnumerable<T>, typ wyliczający jest interfejsem IAsyncEnumerator<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ą podjęte żadne dalsze kroki.
    • W przeciwnym razie zostanie wygenerowany błąd i nie zostaną podjęte żadne dalsze kroki.

Powyższe kroki, jeśli powiedzie się, jednoznacznie tworzą typ kolekcji C, typ modułu wyliczającego E i typ iteracji T. Deklaracja await foreach formularza

await foreach (V v in x) «embedded_statement»

jest wówczas równoważny z:

{
    E e = ((C)(x)).GetAsyncEnumerator();
    try
    {
        while (await e.MoveNextAsync())
        {
            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 await foreach instrukcji), zostanie wygenerowany błąd i nie zostaną wykonane żadne dalsze kroki.

Asynchroniczny enumerator może opcjonalnie uwidocznić metodę DisposeAsync, która może być wywoływana bez argumentów i zwraca coś, które można awaitać i którego GetResult() zwraca void.

Instrukcja foreach formularza

await foreach (T item in enumerable) «embedded_statement»

jest rozszerzany na:

var enumerator = enumerable.GetAsyncEnumerator();
try
{
    while (await enumerator.MoveNextAsync())
    {
       T item = enumerator.Current;
       «embedded_statement»
    }
}
finally
{
    await enumerator.DisposeAsync(); // omitted, along with the try/finally,
                            // if the enumerator doesn't expose DisposeAsync
}

13.10 Instrukcje przeskoku

13.10.1 Ogólne

Instrukcje skoku bezwarunkowo przekazują sterowanie.

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

Lokalizacja, do której instrukcja skoku przenosi sterowanie, jest nazywana celem instrukcji skoku.

Gdy instrukcja skoku występuje w bloku, a jej cel znajduje się poza tym blokiem, mówi się, że instrukcja skoku wychodzi z bloku. Podczas gdy instrukcja skoku może przenosić kontrolę z bloku, nigdy nie może przenieść kontroli do bloku.

Wykonywanie instrukcji skoku jest skomplikowane przez obecność pośrednich try instrukcji. W przypadku braku takich try instrukcji, instrukcja skoku bezwarunkowo przenosi kontrolę z instrukcji skoku do jej celu. W obecności takich interweniujących try instrukcji proces wykonywania staje się bardziej złożony. 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 przepływ sterowania osiągnie punkt końcowy bloku finally, zostanie przeniesiony do bloku finally następnej otaczającej instrukcji try. Ten proces jest powtarzany, aż bloki wszystkich interweniujących finally instrukcji zostaną wykonane.

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 sterowania do docelowej instrukcji skoku. 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 opuszcza najbliższą otaczającą switch, while, do, for lub foreach instrukcję.

break_statement
    : 'break' ';'
    ;

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

Gdy wiele instrukcji switch, while, do, for lub foreach jest zagnieżdżonych jedna w drugą, instrukcja break ma zastosowanie tylko do najbardziej wewnętrznej instrukcji. Aby przenieść kontrolę na wiele poziomów zagnieżdżania, użyj instrukcji goto (§13.10.4).

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

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

  • Jeśli instrukcja break opuszcza jeden lub więcej bloków try ze skojarzonymi blokami finally, kontrola jest początkowo przekazywana do bloku finally najbardziej wewnętrznej instrukcji try. Gdy przepływ sterowania osiągnie punkt końcowy bloku finally, zostanie przeniesiony do bloku finally następnej otaczającej instrukcji try. Ten proces jest powtarzany, aż bloki wszystkich interweniujących finally instrukcji zostaną wykonane.
  • Kontrolka jest przenoszona do elementu docelowego instrukcji break .

Ponieważ instrukcja break 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 instrukcji while, do, for lub foreach.

continue_statement
    : 'continue' ';'
    ;

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

Gdy wiele instrukcji while, do, for lub foreach jest zagnieżdżonych w sobie, instrukcja continue odnosi się tylko do najbardziej wewnętrznej instrukcji. Aby przenieść kontrolę na wiele poziomów zagnieżdżania, użyj instrukcji goto (§13.10.4).

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

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

  • Jeśli instrukcja continue opuszcza jeden lub więcej bloków try ze skojarzonymi blokami finally, kontrola jest początkowo przekazywana do bloku finally najbardziej wewnętrznej instrukcji try. Gdy przepływ sterowania osiągnie punkt końcowy bloku finally, zostanie przeniesiony do bloku finally następnej otaczającej instrukcji try. Ten proces jest powtarzany, aż bloki wszystkich interweniujących finally instrukcji zostaną wykonane.
  • Kontrolka jest przenoszona do elementu docelowego instrukcji continue .

Ponieważ instrukcja continue 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' ';'
    ;

Celem instrukcji gotoidentyfikatora jest instrukcja oznakowana daną etykietą. Jeśli etykieta o podanej nazwie nie istnieje w aktualnej funkcji lub jeśli instrukcja goto nie znajduje się w zakresie etykiety, pojawi się błąd 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 bezpośrednio otaczającej switch instrukcji (§13.8.3), która zawiera etykietę ze stałym wzorcem case podanej stałej wartości i bez warunku ochronnego. Jeśli instrukcja goto case nie jest ujęta w instrukcję switch, jeśli najbliższa otaczająca instrukcja switch nie zawiera takiej instrukcji case, lub jeśli constant_expression nie jest domyślnie konwertowany (§10.2) do typu kontrolnego najbliższej otaczającej instrukcji switch, występuje 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 obejmowana przez instrukcję switch albo gdy najbliższa obejmująca instrukcja switch nie zawiera etykiety default, wystąpi błąd czasu kompilacji.

Instrukcja goto nie może zamknąć finally bloku (§13.11). Gdy instrukcja goto występuje w bloku finally, cel instrukcji goto musi znajdować się w tym samym bloku finally, w przeciwnym razie wystąpi błąd kompilacji.

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

  • Jeśli instrukcja goto opuszcza jeden lub więcej bloków try ze skojarzonymi blokami finally, kontrola jest początkowo przekazywana do bloku finally najbardziej wewnętrznej instrukcji try. Gdy przepływ sterowania osiągnie punkt końcowy bloku finally, zostanie przeniesiony do bloku finally następnej otaczającej instrukcji try. Ten proces jest powtarzany, aż bloki wszystkich interweniujących finally instrukcji zostaną wykonane.
  • Kontrolka jest przenoszona do elementu docelowego instrukcji goto .

Ponieważ instrukcja goto 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 ';'
    ;

Instrukcja_return bez wyrażenia jest nazywana brak_wartości; ref wyrażenie zawierające wyrażenie jest nazywane zwracanie-przez-referencję, a jedno zawierające tylko wyrażenie jest nazywane zwracanie-przez-wartość.

Jest to błąd czasu kompilacji, jeśli metoda zadeklarowana jako zwracająca przez wartość lub przez referencję nie zwraca żadnej wartości (§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ć zwracania przez wartość w metodzie zadeklarowanej jako nie zwracająca wartości lub zwracająca przez referencję.

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

Mówi się, że element członkowski funkcji oblicza wartość, jeśli jest metodą zwracającą wartość (§15.6.11), uzyskuje metodę dostępu zwracającą wartość od właściwości lub indeksatora, lub jest operatorem zdefiniowanym przez użytkownika. Elementy członkowskie funkcji, które nie zwracają wartości, nie obliczają wartości i są metodami z efektywnym typem zwrotnym void, ustawnikami właściwości i indeksatorów, dodawnikami i usuwaczami zdarzeń, konstruktorami wystąpień, konstruktorami statycznymi i finalizatorami. Elementy członkowskie funkcji, które są zwracane przez ref, nie obliczają wartości.

W przypadku zwracanej wartości musi istnieć niejawna konwersja (§10.2) z typu wyrażenia do efektywnego typu zwracanego (§15.6.11) członka funkcji zawierającego. W przypadku zwrotu przez referencję, konwersja tożsamości (§10.2.2) musi istnieć między typem wyrażenia a efektywnym typem zwracanym zawierającego elementu członkowskiego funkcji.

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

Jest to błąd czasu kompilacji, gdy instrukcja return pojawia się w bloku finally (§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 zwracanie przez referencję w metodzie otaczającej zawiera readonly, zmienna wynikowa jest tylko do odczytu.
  • return Jeśli instrukcja jest ujęta przez jeden lub try więcej catch bloków ze skojarzonymi finally blokami, kontrolka jest początkowo przenoszona do finally bloku najbardziej wewnętrznej try instrukcji. Gdy przepływ sterowania osiągnie punkt końcowy bloku finally, zostanie przeniesiony do bloku finally następnej otaczającej instrukcji try. Ten proces jest powtarzany do momentu, aż bloki wszystkich obejmujących instrukcji finally zostały wykonane.
  • 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 temacie (§15.14.3).

Ponieważ instrukcja return 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 zanim zostanie zgłoszona. Jeśli wynikiem konwersji jest null, zostanie zgłoszony System.NullReferenceException zamiast tego.

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

Ponieważ instrukcja throw 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 rzutu 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 S instrukcji tryi kończącej się najbardziej zewnętrzną try instrukcją, oceniane są następujące kroki:

    • Jeśli blok try obejmuje punkt rzutu i jeśli S ma co najmniej jedną klauzulę S, klauzule catch są badane w kolejności występowania, aby znaleźć odpowiednią procedurę obsługi dla wyjątku. Pierwsza catch klauzula, która określa typ wyjątku T (lub parametr typu, który w czasie wykonywania oznacza typ wyjątku T) w taki sposób, że typ czasu wykonywania E pochodzi z T, jest uznawana 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, jeśli filtr wyjątku catch oceni się na trueprawda. Klauzula ogólna catch (§13.11) jest uważana za zgodną z dowolnym typem wyjątku. Jeśli zostanie znaleziona odpowiednia klauzula catch, propagacja wyjątku jest zakończona przez przeniesienie kontroli do bloku tej klauzuli catch.
    • W przeciwnym razie, jeśli blok try lub blok catchS otacza punkt rzutu i jeśli S ma blok finally, kontrola jest przekazywana do bloku finally. Jeśli blok finally zgłasza inny wyjątek, przetwarzanie bieżącego wyjątku zostanie zakończone. W przeciwnym razie, gdy kontrola osiągnie punkt końcowy bloku finally, przetwarzanie bieżącego wyjątku będzie 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 asynchroniczna i zwraca zadanie, wyjątek jest rejestrowany w zadaniu zwrotnym, które jest umieszczane w stanie błędnym lub anulowanym zgodnie z opisem w §15.14.3.

    • Jeśli bieżąca funkcja jest asynchroniczna i zwracająca void, kontekst synchronizacji bieżącego wątku zostaje powiadomiony zgodnie z opisem w §15.14.4.

  • Jeśli przetwarzanie wyjątku kończy wszystkie wywołania funkcji w bieżącym wątku, wskazując, że wątek nie ma obsługi dla wyjątku, to wątek zostaje 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
    ;

Instrukcja try składa się ze słowa kluczowego try, po którym następuje blok, zero lub więcej klauzul catch, a następnie opcjonalna klauzula finally. Istnieje co najmniej jedna catch_clause lub finally_clause.

W exception_specifiertyp, lub jego efektywna klasa bazowa, jeśli jest typem_parameter, musi być System.Exception lub typem, 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 nie określająca ani typu wyjątku, ani nazwy 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órej ta pierwsza może również przechwytywać wyjątki z innych języków. notatka końcowa

Aby zlokalizować procedurę obsługi wyjątku, catch klauzule są badane w kolejności leksykalnej. Jeśli klauzula catch określa typ, ale nie filtr wyjątku, jest błędem czasu kompilacji, jeżeli późniejsza catch klauzula tej samej try instrukcji określa typ taki sam jak, lub pochodny 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 może służyć do ponownego wyrzucenia wyjątku przechwyconego przez blok catch. Przypisania do zmiennej wyjątku nie zmieniają wyjątku, który jest ponownie rzucany.

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 rzucił e zamiast ponownego rzucenia bieżącego wyjątku, wygenerowane dane wyjściowe wyglądałyby następująco:

Exception in F: G
Exception in Main: F

przykład końcowy

Jest to błąd czasu kompilacji, gdy instrukcja break, continue lub goto przenosi kontrolę poza blok finally. Gdy break, continue lub goto występuje w finally bloku, element docelowy instrukcji musi znajdować się w tym samym finally bloku, w przeciwnym wypadku wystąpi błąd czasu kompilacji.

Jest to błąd czasu kompilacji, jeśli return instrukcja występuje w finally bloku.

Gdy wykonanie osiągnie instrukcję try, sterowanie zostaje przekazane do bloku try. Jeśli kontrola osiągnie końcowy punkt bloku try bez propagacji wyjątku, zostanie przeniesiona do bloku finally, jeśli taki istnieje. Jeśli żaden blok finally nie istnieje, kontrola zostanie przekazana 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 ocena boolean_expression dla exception_filter zgłasza wyjątek, ten wyjątek zostanie przechwycony, a filtr wyjątku zwróci wartość false.

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

Jeśli został rozpropagowany wyjątek i nie zostanie znaleziona żadna zgodna catch klauzula, sterowanie zostanie przekazane do bloku finally, jeśli taki istnieje. Wyjątek jest propagowany do następnej znajdującej się najbliżej instrukcji try.

Instrukcje bloku finally są zawsze wykonywane, gdy kontrolka pozostawia 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 kontrola osiągnie punkt końcowy bloku finally bez propagacji wyjątku, zostanie przeniesiona do punktu końcowego instrukcji try.

Jeśli podczas wykonywania bloku finally zgłoszony zostanie wyjątek i nie zostanie on przechwycony w tym samym bloku finally, to wyjątek jest propagowany do następnej otaczającej instrukcji try. Jeśli inny wyjątek był w trakcie propagacji, ten wyjątek zostanie utracony. Proces propagowania wyjątku jest omówiony 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 zbadanie obejmujących catch klauzul, stosując wszelkie filtry wyjątków. Następnie klauzula finally w Method jest wykonywana przed przeniesieniem kontroli do otaczającej pasującej klauzuli catch. Wynikowe dane wyjściowe to:

Filter
Finally
Catch

przykład końcowy

Blok try instrukcji try jest osiągalny, jeśli instrukcja try 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 instrukcja try 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.
  • Jeśli blok finally jest obecny, punkt końcowy bloku finally jest osiągalny.

13.12 Sprawdzane i niesprawdzane 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 wykonuje się niejawnej konwersji boksu (§10.2.9) dla wyrażenialock instrukcji, wobec czego oznaczenie przez wyrażenie wartości typu value_type jest błędem czasu kompilacji.

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 tą różnicą, że x jest obliczana 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 (IAsyncDisposabledla strumieni asynchronicznych), który zawiera pojedynczą metodę bez parametrów o nazwie Dispose (DisposeAsync dla strumieni asynchronicznych). Kod używający zasobu może wywołać Dispose, aby wskazać, że zasób nie jest już potrzebny.

Jeśli forma resource_acquisition jest local_variable_declaration , typ local_variable_declaration musi być dynamic albo typ, który może być niejawnie przekonwertowany na System.IDisposable (IAsyncDisposable dla strumieni asynchronicznych). Jeśli postać resource_acquisition jest wyrażeniem, to wyrażenie jest niejawnie konwertowane na System.IDisposable (IAsyncDisposable dla strumieni asynchronicznych).

Zmienne lokalne zadeklarowane w resource_acquisition są tylko do odczytu i zawierają inicjator. Błąd czasu kompilacji występuje, jeśli instrukcja osadzona próbuje zmodyfikować te zmienne lokalne (za pomocą przypisania lub operatorów ++ i --), pobrać ich adres 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. Jeśli zasób null zostanie pozyskany, nie zostanie wykonane wywołanie metody Dispose (DisposeAsync dla strumieni asynchronicznych) i nie zostanie zgłoszony żaden wyjątek. Jeśli zasób jest typu dynamic , jest dynamicznie konwertowany przez niejawną konwersję dynamiczną (§10.2.10) na IDisposable (IAsyncDisposable dla strumieni asynchronicznych) podczas pozyskiwania w celu zapewnienia pomyślnej konwersji przed użyciem i usunięciem.

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 resourceSystem.IDisposable nie powoduje wystąpienia boksu.

W przeciwnym razie, gdy ResourceType jest dynamic, 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.

Oświadczenie using w formie:

using («expression») «statement»

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

Gdy resource_acquisition ma postać 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);
            }
        }
    }
}

Ponieważ klasy TextWriter i TextReader implementują interfejs IDisposable, przykład może użyć instrukcji using, aby upewnić się, że plik 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) w celu uzyskania wartości obiektu wyliczającego (§15.15.5) lub obiektu wyliczalnego (§15.15.6) iteratora lub sygnalizatora końca 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że pojawić się instrukcja yield, jak opisano poniżej.

  • Jest to błąd czasu kompilacji, jeśli instrukcja yield (w dowolnej formie) pojawi się poza method_body, operator_body lub accessor_body.
  • Jest błędem czasu kompilacji, gdy instrukcja yield (w dowolnej formie) pojawi się wewnątrz funkcji anonimowej.
  • Wystąpienie instrukcji yield (w dowolnej formie) w klauzuli finally instrukcji try jest błędem czasu kompilacji.
  • Jest to błąd czasu kompilacji, gdy instrukcja yield return pojawia się w dowolnym miejscu w instrukcji try zawierającej jakiekolwiek 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) musi istnieć z typu wyrażenia użytego w yield return instrukcji do typu zwracanego (§15.15.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 nie są wykonywane w tym momencie.
  • 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 objęta przez co najmniej jeden try blok ze skojarzonymi finally blokami, sterowanie jest początkowo przenoszone do finally bloku najgłębszej try instrukcji. Gdy przepływ sterowania osiągnie punkt końcowy bloku finally, zostanie przeniesiony do bloku finally następnej otaczającej instrukcji try. Ten proces jest powtarzany do momentu, aż bloki wszystkich obejmujących instrukcji finally zostały wykonane.
  • Kontrolka jest zwracana do obiektu wywołującego blok iteratora. Jest to metoda MoveNext lub metoda Dispose obiektu wyliczającego.

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