13 instrukcji
13.1 Ogólne
Język C# zawiera różne instrukcje.
Uwaga: Większość tych instrukcji będzie znana deweloperom, którzy programowali w języku C i C++. notatka końcowa
statement
: labeled_statement
| declaration_statement
| embedded_statement
;
embedded_statement
: block
| empty_statement
| expression_statement
| selection_statement
| iteration_statement
| jump_statement
| try_statement
| checked_statement
| unchecked_statement
| lock_statement
| using_statement
| yield_statement
| unsafe_statement // unsafe code support
| fixed_statement // unsafe code support
;
unsafe_statement (§23.2) i fixed_statement (§23.7) są dostępne tylko w niebezpiecznym kodzie (§23).
Embedded_statement nonterminal jest używany do instrukcji, które pojawiają się w innych instrukcjach. Użycie embedded_statement, a nie instrukcji wyklucza użycie instrukcji deklaracji i instrukcji oznaczonych etykietami w tych kontekstach.
Przykład: kod
void F(bool b) { if (b) int i = 44; }
powoduje błąd czasu kompilacji, ponieważ
if
instrukcja wymaga embedded_statement, a nie instrukcji dla jejif
gałęzi. Gdyby ten kod był dozwolony, zmiennai
zostanie zadeklarowana, ale nigdy nie można jej użyć. Należy jednak pamiętać, że umieszczeniei
deklaracji "w bloku" jest prawidłowe.przykład końcowy
13.2 Punkty końcowe i osiągalność
Każda instrukcja ma punkt końcowy. Intuicyjnie mówiąc, punkt końcowy instrukcji to lokalizacja, która jest natychmiast zgodna z instrukcją . Reguły wykonywania instrukcji złożonych (instrukcje zawierające instrukcje osadzone) określają akcję wykonywaną, gdy kontrolka osiągnie punkt końcowy osadzonej instrukcji.
Przykład: gdy kontrolka osiągnie punkt końcowy instrukcji w bloku, kontrolka zostanie przeniesiona do następnej instrukcji w bloku. przykład końcowy
Jeśli oświadczenie może zostać osiągnięte przez wykonanie, mówi się, że oświadczenie jest osiągalne. Z drugiej strony, jeśli nie ma możliwości wykonania oświadczenia, oświadczenie mówi się, że nie jest osiągalne.
Przykład: w poniższym kodzie
void F() { Console.WriteLine("reachable"); goto Label; Console.WriteLine("unreachable"); Label: Console.WriteLine("reachable"); }
drugie wywołanie elementu Console.WriteLine nie jest osiągalne, ponieważ nie ma możliwości wykonania instrukcji.
przykład końcowy
Ostrzeżenie jest zgłaszane, jeśli instrukcja inna niż throw_statement, blok lub empty_statement jest niedostępna. Nie jest to w szczególności błąd instrukcji, która nie jest osiągalna.
Uwaga: Aby określić, czy określona instrukcja lub punkt końcowy jest osiągalna, kompilator wykonuje analizę przepływu zgodnie z regułami dostępności zdefiniowanymi dla każdej instrukcji. Analiza przepływu uwzględnia wartości wyrażeń stałych (§12.23), które kontrolują zachowanie instrukcji, ale nie są brane pod uwagę możliwe wartości wyrażeń niestałych. Innymi słowy, na potrzeby analizy przepływu sterowania wyrażenie niestałych danego typu jest uznawane za dowolne możliwe wartości tego typu.
W przykładzie
void F() { const int i = 1; if (i == 2) Console.WriteLine("unreachable"); }
wyrażenie logiczne instrukcji
if
jest wyrażeniem stałym, ponieważ oba operandy==
operatora są stałymi. Ponieważ wyrażenie stałe jest obliczane w czasie kompilacji, generowanie wartościfalse
powodujeConsole.WriteLine
, że wywołanie jest uznawane za niedostępne. Jeślii
jednak zostanie zmieniona na zmienną lokalnąvoid F() { int i = 1; if (i == 2) Console.WriteLine("reachable"); }
Console.WriteLine
wywołanie jest uważane za osiągalne, mimo że w rzeczywistości nigdy nie zostanie wykonane.notatka końcowa
Blok elementu członkowskiego funkcji lub funkcji anonimowej jest zawsze uznawany za osiągalny. Oceniając kolejno reguły osiągalności każdej instrukcji w bloku, można określić osiągalność dowolnej danej instrukcji.
Przykład: w poniższym kodzie
void F(int x) { Console.WriteLine("start"); if (x < 0) Console.WriteLine("negative"); }
osiągalność sekundy
Console.WriteLine
jest określana w następujący sposób:
- Pierwsza
Console.WriteLine
instrukcja wyrażenia jest osiągalna, ponieważ blokF
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 pierwszejConsole.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 instrukcjiif
nie ma stałej wartościfalse
.przykład końcowy
Istnieją dwie sytuacje, w których jest to błąd czasu kompilacji, aby punkt końcowy instrukcji był osiągalny:
switch
Ponieważ instrukcja nie zezwala na przejście sekcji przełącznika do następnej sekcji przełącznika, jest to błąd czasu kompilacji dla punktu końcowego listy instrukcji sekcji przełącznika, która ma być osiągalna. Jeśli wystąpi ten błąd, zazwyczaj oznacza to, żebreak
brakuje instrukcji.Jest to błąd czasu kompilacji dla punktu końcowego bloku elementu członkowskiego funkcji lub funkcji anonimowej, która oblicza wartość, która ma być osiągalna. Jeśli wystąpi ten błąd, zazwyczaj oznacza to, że
return
brakuje instrukcji (§13.10.5).
Bloki 13.3
13.3.1 Ogólne
Blok zezwala na pisanie wielu instrukcji w kontekstach, w których dozwolona jest pojedyncza instrukcja.
block
: '{' statement_list? '}'
;
Blok składa się z opcjonalnego statement_list (§13.3.2), ujętego w nawiasy klamrowe. Jeśli lista instrukcji zostanie pominięta, blok jest mówi się, że jest pusty.
Blok może zawierać oświadczenia deklaracji (§13.6). Zakres zmiennej lokalnej lub stałej zadeklarowanej w bloku jest blokiem.
Blok jest wykonywany w następujący sposób:
- Jeśli blok jest pusty, kontrolka jest przenoszona do punktu końcowego bloku.
- Jeśli blok nie jest pusty, kontrolka zostanie przeniesiona na listę instrukcji. Gdy kontrolka i jeśli osiągnie punkt końcowy listy instrukcji, kontrolka zostanie przeniesiona do punktu końcowego bloku.
Lista instrukcji bloku jest osiągalna, jeśli sam blok jest osiągalny.
Punkt końcowy bloku jest osiągalny, jeśli blok jest pusty lub punkt końcowy listy instrukcji jest osiągalny.
Blok zawierający co najmniej jedną yield
instrukcję (§13.15) jest nazywany blokiem iteratora. Bloki iteracyjne służą do implementowania składowych funkcji jako iteratorów (§15.14). Niektóre dodatkowe ograniczenia dotyczą bloków iteratora:
- Jest to błąd czasu kompilacji instrukcji
return
wyświetlanej w bloku iteratora (aleyield return
instrukcje są dozwolone). - Jest to błąd czasu kompilacji dla bloku iteratora zawierającego niebezpieczny kontekst (§23.2). Blok iteratora zawsze definiuje bezpieczny kontekst, nawet jeśli jego deklaracja jest zagnieżdżona w niebezpiecznym kontekście.
13.3.2 Listy instrukcji
Lista instrukcji składa się z co najmniej jednej instrukcji napisanej w sekwencji. Listy instrukcji występują w blokach(§13.3) i w switch_blocks (§13.8.3).
statement_list
: statement+
;
Lista instrukcji jest wykonywana przez przeniesienie kontrolki do pierwszej instrukcji. Gdy kontrolka i jeśli osiągnie punkt końcowy instrukcji, kontrolka zostanie przeniesiona do następnej instrukcji. Gdy kontrolka i jeśli osiągnie punkt końcowy ostatniej instrukcji, kontrolka zostanie przeniesiona do punktu końcowego listy instrukcji.
Instrukcja na liście instrukcji jest osiągalna, jeśli co najmniej jedna z następujących wartości jest prawdziwa:
- Instrukcja jest pierwszą instrukcją, a sama lista instrukcji jest osiągalna.
- Punkt końcowy poprzedniej instrukcji jest osiągalny.
- Instrukcja jest instrukcją oznaczona etykietą, a etykieta jest przywoływane przez osiągalną
goto
instrukcję.
Punkt końcowy listy instrukcji jest osiągalny, jeśli punkt końcowy ostatniej instrukcji na liście jest osiągalny.
13.4 Pusta instrukcja
Empty_statement nic nie robi.
empty_statement
: ';'
;
Pusta instrukcja jest używana, gdy nie ma żadnych operacji do wykonania w kontekście, w którym jest wymagana instrukcja.
Wykonanie pustej instrukcji po prostu przenosi kontrolkę do punktu końcowego instrukcji. W związku z tym punkt końcowy pustej instrukcji jest osiągalny, jeśli pusta instrukcja jest osiągalna.
Przykład: Pusta instrukcja może być używana podczas pisania
while
instrukcji z treścią o wartości null:bool ProcessMessage() {...} void ProcessMessages() { while (ProcessMessage()) ; }
Ponadto pusta instrukcja może służyć do deklarowania etykiety tuż przed zamknięciem
}
"" bloku:void F(bool done) { ... if (done) { goto exit; } ... exit: ; }
przykład końcowy
13.5 Instrukcje oznaczone etykietą
Labeled_statement zezwala na prefiks instrukcji z etykietą. Instrukcje oznaczone etykietami są dozwolone w blokach, ale nie są dozwolone jako instrukcje osadzone.
labeled_statement
: identifier ':' statement
;
Instrukcja oznaczona etykietą deklaruje etykietę o nazwie nadanej przez identyfikator. Zakres etykiety to cały blok, w którym jest zadeklarowana etykieta, w tym wszystkie zagnieżdżone bloki. Jest to błąd czasu kompilacji dla dwóch etykiet o tej samej nazwie, aby mieć nakładające się zakresy.
Etykietę można przywoływać z goto
instrukcji (§13.10.4) w zakresie etykiety.
Uwaga: oznacza to, że
goto
instrukcje mogą przenosić kontrolę wewnątrz bloków i z bloków, ale nigdy nie do bloków. notatka końcowa
Etykiety mają własną przestrzeń deklaracji i nie zakłócają innych identyfikatorów.
Przykład: przykład
int F(int x) { if (x >= 0) { goto x; } x = -x; x: return x; }
jest prawidłowy i używa nazwy x zarówno jako parametru, jak i etykiety.
przykład końcowy
Wykonanie instrukcji oznaczonej etykietą odpowiada dokładnie wykonaniu instrukcji po etykiecie.
Oprócz osiągalności zapewnianej przez normalny przepływ sterowania instrukcja oznaczona etykietą jest osiągalna, jeśli etykieta jest przywoływany przez instrukcję osiągalną goto
, chyba że goto
instrukcja znajduje się wewnątrz try
bloku lub catch
bloku try_statement , który zawiera finally
blok, którego punkt końcowy jest nieosiągalny, a instrukcja oznaczona etykietą znajduje się poza try_statement.
Instrukcje deklaracji 13.6
13.6.1 Ogólne
Declaration_statement deklaruje co najmniej jedną zmienną lokalną, co najmniej jedną stałą lokalną lub funkcję lokalną. Instrukcje deklaracji są dozwolone w blokach i blokach przełączników, ale nie są dozwolone jako instrukcje osadzone.
declaration_statement
: local_variable_declaration ';'
| local_constant_declaration ';'
| local_function_declaration
;
Zmienna lokalna jest deklarowana przy użyciu local_variable_declaration (§13.6.2). Stała lokalna jest deklarowana przy użyciu local_constant_declaration (§13.6.3). Funkcja lokalna jest deklarowana przy użyciu local_function_declaration (§13.6.4).
Zadeklarowane nazwy są wprowadzane do najbliższej otaczającej przestrzeni deklaracji (§7.3).
13.6.2 Deklaracje zmiennych lokalnych
13.6.2.1 Ogólne
Local_variable_declaration deklaruje co najmniej jedną zmienną lokalną.
local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
| explicitly_typed_ref_local_variable_declaration
;
Niejawnie wpisane deklaracje zawierają słowo kluczowe kontekstowe (§6.4.4), var
co powoduje niejednoznaczność składni między trzema kategoriami, które są rozpoznawane w następujący sposób:
- Jeśli nie ma typu o nazwie
var
w zakresie, a dane wejściowe są zgodne implicitly_typed_local_variable_declaration , zostanie wybrany; - W przeciwnym razie, jeśli typ o nazwie
var
jest w zakresie, implicitly_typed_local_variable_declaration nie jest uważany za możliwe dopasowanie.
W local_variable_declaration każda zmienna jest wprowadzana przez deklarator, który jest jednym z implicitly_typed_local_variable_declarator, explicitly_typed_local_variable_declarator lub ref_local_variable_declarator dla niejawnie wpisanych, jawnie wpisanych i ref zmiennych lokalnych. Deklarator definiuje nazwę (identyfikator) i wartość początkową , jeśli istnieje, wprowadzonej zmiennej.
Jeśli w deklaracji istnieje wiele deklaratorów, są one przetwarzane, w tym wszelkie wyrażenia inicjacyjne, w kolejności od lewej do prawej (§9.4.4.5).
Uwaga: w przypadku local_variable_declaration, które nie występują jako for_initializer (§13.9.4) lub resource_acquisition (§13.14), ta lewa do prawej kolejności jest równoważna każdemu deklaratorowi znajdującemu się w osobnym local_variable_declaration. Na przykład:
void F() { int x = 1, y, z = x * 2; }
jest odpowiednikiem:
void F() { int x = 1; int y; int z = x * 2; }
notatka końcowa
Wartość zmiennej lokalnej jest uzyskiwana w wyrażeniu przy użyciu simple_name (§12.8.4). Zmienna lokalna musi być zdecydowanie przypisana (§9.4) w każdej lokalizacji, w której uzyskuje się jej wartość. Każda zmienna lokalna wprowadzona przez local_variable_declaration jest początkowo nieprzypisane (§9.4.3). Jeśli deklarator ma wyrażenie inicjacyjne, wprowadzona zmienna lokalna jest klasyfikowana jako przypisana na końcu deklaratora (§9.4.4.5).
Zakres zmiennej lokalnej wprowadzonej przez local_variable_declaration jest definiowany w następujący sposób (§7.7):
- Jeśli deklaracja występuje jako for_initializer , zakresem jest for_initializer, for_condition, for_iterator i embedded_statement (§13.9.4);
- Jeśli deklaracja występuje jako resource_acquisition zakres jest najbardziej zewnętrznym blokiem semantycznie równoważnego rozszerzenia using_statement (§13.14);
- W przeciwnym razie zakres to blok, w którym występuje deklaracja.
Jest to błąd odwoływania się do zmiennej lokalnej według nazwy w pozycji tekstowej, która poprzedza deklaratora lub w dowolnym wyrażeniu inicjującym w deklaratorze. W zakresie zmiennej lokalnej jest to błąd czasu kompilacji, aby zadeklarować inną zmienną lokalną, funkcję lokalną lub stałą o tej samej nazwie.
Kontekst ref-safe-context (§9.7.2) zmiennej lokalnej ref jest kontekstem ref-safe-context inicjowania variable_reference. Kontekst ref-safe-context zmiennych lokalnych innych niż ref jest blok-deklaracji.
13.6.2.2 Niejawnie wpisane deklaracje zmiennych lokalnych
implicitly_typed_local_variable_declaration
: 'var' implicitly_typed_local_variable_declarator
| ref_kind 'var' ref_local_variable_declarator
;
implicitly_typed_local_variable_declarator
: identifier '=' expression
;
Implicitly_typed_local_variable_declaration wprowadza pojedynczą zmienną lokalną, identyfikator. Wyrażenie lub variable_reference musi mieć typ czasu kompilacji, T
. Pierwsza alternatywa deklaruje zmienną z początkową wartością wyrażenia; jego typem jest, gdy T
jest T?
typem referencyjnym bez wartości null, w przeciwnym razie jego typem jest T
. Druga alternatywa deklaruje zmienną ref z początkową wartością ref
variable_reference; jego typem jest, gdy T
jest ref T?
typem referencyjnym, który nie może zawierać wartości null, w przeciwnym razie jego typem jest ref T
. (ref_kind jest opisany w §15.6.1.)
Przykład:
var i = 5; var s = "Hello"; var d = 1.0; var numbers = new int[] {1, 2, 3}; var orders = new Dictionary<int,Order>(); ref var j = ref i; ref readonly var k = ref i;
Niejawnie typizowane deklaracje zmiennych lokalnych są dokładnie równoważne następującym jawnie wpisanym deklaracjom:
int i = 5; string s = "Hello"; double d = 1.0; int[] numbers = new int[] {1, 2, 3}; Dictionary<int,Order> orders = new Dictionary<int,Order>(); ref int j = ref i; ref readonly int k = ref i;
Poniżej przedstawiono niepoprawne niejawnie wpisane deklaracje zmiennych lokalnych:
var x; // Error, no initializer to infer type from var y = {1, 2, 3}; // Error, array initializer not permitted var z = null; // Error, null does not have a type var u = x => x + 1; // Error, anonymous functions do not have a type var v = v++; // Error, initializer cannot refer to v itself
przykład końcowy
13.6.2.3 Jawnie wpisane deklaracje zmiennych lokalnych
explicitly_typed_local_variable_declaration
: type explicitly_typed_local_variable_declarators
;
explicitly_typed_local_variable_declarators
: explicitly_typed_local_variable_declarator
(',' explicitly_typed_local_variable_declarator)*
;
explicitly_typed_local_variable_declarator
: identifier ('=' local_variable_initializer)?
;
local_variable_initializer
: expression
| array_initializer
;
Explicity_typed_local_variable_declaration wprowadza co najmniej jedną zmienną lokalną z określonym typem.
Jeśli local_variable_initializer jest obecny, jego typ jest odpowiedni zgodnie z regułami prostego przypisania (§12.21.2) lub inicjowania tablicy (§17.7), a jego wartość jest przypisywana jako początkowa wartość zmiennej.
13.6.2.4 Jawnie wpisane deklaracje zmiennych lokalnych ref
explicitly_typed_ref_local_variable_declaration
: ref_kind type ref_local_variable_declarators
;
ref_local_variable_declarators
: ref_local_variable_declarator (',' ref_local_variable_declarator)*
;
ref_local_variable_declarator
: identifier '=' 'ref' variable_reference
;
Inicjowanie variable_reference ma typ i spełnia te same wymagania co w przypadku przypisania ref (§12.21.3).
Jeśli ref_kind to ref readonly
, zadeklarowane identyfikatory są odwołaniami do zmiennych, które są traktowane jako tylko do odczytu. W przeciwnym razie, jeśli ref_kind to ref
, identyfikatory zadeklarowane są odwołaniami do zmiennych, które można zapisywać.
Jest to błąd czasu kompilacji, aby zadeklarować zmienną lokalną ref lub zmienną ref struct
typu w metodzie zadeklarowanej przy użyciu method_modifier async
lub w iteratorze (§15.14).
13.6.3 Deklaracje stałych lokalnych
Local_constant_declaration deklaruje co najmniej jedną stałą lokalną.
local_constant_declaration
: 'const' type constant_declarators
;
constant_declarators
: constant_declarator (',' constant_declarator)*
;
constant_declarator
: identifier '=' constant_expression
;
Typ local_constant_declaration określa typ stałych wprowadzonych przez deklarację. Po typie następuje lista constant_declarators, z których każda wprowadza nową stałą. Constant_declarator składa się z identyfikatora, który nazywa stałą, po której następuje token "=
", a następnie constant_expression (§12.23), który daje wartość stałej.
Typ i constant_expression lokalnej deklaracji stałej są zgodne z tymi samymi zasadami co deklaracja stałej składowej (§15.4).
Wartość stałej lokalnej jest uzyskiwana w wyrażeniu przy użyciu simple_name (§12.8.4).
Zakres stałej lokalnej to blok, w którym występuje deklaracja. Jest to błąd podczas odwoływania się do stałej lokalnej w pozycji tekstowej, która poprzedza koniec constant_declarator.
Lokalna deklaracja stałej, która deklaruje wiele stałych, jest równoważna wielokrotnym deklaracjom pojedynczych stałych o tym samym typie.
13.6.4 Lokalne deklaracje funkcji
Local_function_declaration deklaruje funkcję lokalną.
local_function_declaration
: local_function_modifier* return_type local_function_header
local_function_body
| ref_local_function_modifier* ref_kind ref_return_type
local_function_header ref_local_function_body
;
local_function_header
: identifier '(' parameter_list? ')'
| identifier type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause*
;
local_function_modifier
: ref_local_function_modifier
| 'async'
;
ref_local_function_modifier
: 'static'
| unsafe_modifier // unsafe code support
;
local_function_body
: block
| '=>' null_conditional_invocation_expression ';'
| '=>' expression ';'
;
ref_local_function_body
: block
| '=>' 'ref' variable_reference ';'
;
Uwaga gramatyczny: W przypadku rozpoznawania local_function_body, jeśli stosowane są zarówno null_conditional_invocation_expression, jak i alternatywy wyrażeń, należy wybrać pierwszy. (§15.6.1)
Przykład: istnieją dwa typowe przypadki użycia funkcji lokalnych: metody iteracyjne i metody asynchroniczne. W metodach iteratora wszelkie wyjątki są obserwowane tylko podczas wywoływania kodu wyliczającego zwróconą sekwencję. W metodach asynchronicznych wszelkie wyjątki są obserwowane tylko wtedy, gdy zwracane zadanie jest oczekiwane. W poniższym przykładzie pokazano oddzielenie walidacji parametrów od implementacji iteratora przy użyciu funkcji lokalnej:
public static IEnumerable<char> AlphabetSubset(char start, char end) { if (start < 'a' || start > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter"); } if (end < 'a' || end > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); } if (end <= start) { throw new ArgumentException( $"{nameof(end)} must be greater than {nameof(start)}"); } return AlphabetSubsetImplementation(); IEnumerable<char> AlphabetSubsetImplementation() { for (var c = start; c < end; c++) { yield return c; } } }
przykład końcowy
Jeśli nie określono inaczej niżej, semantyka wszystkich elementów gramatycznych jest taka sama jak w przypadku method_declaration (§15.6.1), odczytywana w kontekście funkcji lokalnej zamiast metody.
Identyfikator local_function_declaration musi być unikatowy w swoim zadeklarowanym zakresie blokowym, w tym wszelkie otaczające przestrzenie deklaracji zmiennych lokalnych. Jedną z konsekwencji jest to, że przeciążone local_function_declarations są niedozwolone.
Local_function_declaration może zawierać jeden async
modyfikator (§15.15) i jeden unsafe
modyfikator (§23.1). Jeżeli deklaracja zawiera async
modyfikator, typ zwracany musi być void
lub typem «TaskType»
(§15.15.1). Jeśli deklaracja zawiera static
modyfikator, funkcja jest statyczną funkcją lokalną; w przeciwnym razie jest to funkcja niestatyczna lokalna. Jest to błąd czasu kompilacji dla type_parameter_list lub parameter_list zawierać atrybuty. Jeśli funkcja lokalna jest zadeklarowana w niebezpiecznym kontekście (§23.2), funkcja lokalna może zawierać niebezpieczny kod, nawet jeśli deklaracja funkcji lokalnej nie zawiera unsafe
modyfikatora.
Funkcja lokalna jest zadeklarowana w zakresie bloku. Funkcja lokalna niestatyczna może przechwytywać zmienne z otaczającego zakresu, podczas gdy statyczna funkcja lokalna nie może (więc nie ma dostępu do ujęć lokalnych, parametrów, niestacjonanych funkcji lokalnych lub this
). Jest to błąd czasu kompilacji, jeśli przechwycona zmienna jest odczytywana przez treść niestacjonanej funkcji lokalnej, ale nie jest zdecydowanie przypisana przed każdym wywołaniem funkcji. Kompilator określa, które zmienne są zdecydowanie przypisane po powrocie (§9.4.4.33).
Gdy typ jest typem this
struktury, jest to błąd czasu kompilacji dla treści funkcji lokalnej w celu uzyskania dostępu do this
elementu . Jest to prawda, czy dostęp jest jawny (jak w this.x
pliku ) lub niejawny (tak jak w przypadku, gdy x
x
jest członkiem wystąpienia struktury). Ta reguła uniemożliwia tylko taki dostęp i nie ma wpływu na to, czy wyszukiwanie elementów członkowskich powoduje element członkowski struktury.
Jest to błąd czasu kompilacji dla treści funkcji lokalnej zawierającej instrukcję, break
instrukcję goto
lub continue
instrukcję, której element docelowy znajduje się poza treścią funkcji lokalnej.
Uwaga: powyższe reguły dla
this
funkcji anonimowych igoto
dublowania ich w §12.19.3. notatka końcowa
Funkcja lokalna może być wywoływana z punktu leksyktycznego przed jego deklaracją. Jednak jest to błąd czasu kompilacji funkcji, który ma być zadeklarowany leksykalnie przed deklaracją zmiennej używanej w funkcji lokalnej (§7.7).
Jest to błąd czasu kompilacji dla funkcji lokalnej, aby zadeklarować parametr, parametr typu lub zmienną lokalną o tej samej nazwie co zadeklarowany w dowolnej otaczającej przestrzeni deklaracji zmiennej lokalnej.
Lokalne jednostki funkcji są zawsze dostępne. Punkt końcowy deklaracji funkcji lokalnej jest osiągalny, jeśli punkt początkowy deklaracji funkcji lokalnej jest osiągalny.
Przykład: W poniższym przykładzie treść
L
obiektu jest osiągalna, mimo że punktL
początkowy elementu nie jest osiągalny. Ponieważ punktL
początkowy elementu nie jest osiągalny, instrukcja po punkcie końcowymL
nie jest osiągalna:class C { int M() { L(); return 1; // Beginning of L is not reachable int L() { // The body of L is reachable return 2; } // Not reachable, because beginning point of L is not reachable return 3; } }
Innymi słowy, lokalizacja lokalnej deklaracji funkcji nie ma wpływu na osiągalność żadnych instrukcji w funkcji zawierającej. przykład końcowy
Jeśli typ argumentu funkcji lokalnej to dynamic
, wywoływana funkcja jest rozpoznawana w czasie kompilacji, a nie w czasie wykonywania.
Funkcja lokalna nie może być używana w drzewie wyrażeń.
Statyczna funkcja lokalna
- Może odwoływać się do statycznych elementów członkowskich, parametrów typu, stałych definicji i statycznych funkcji lokalnych z otaczającego zakresu.
- Nie odwołuje
this
się anibase
do elementów członkowskich wystąpienia z niejawnegothis
odwołania, ani zmiennych lokalnych, parametrów ani niestacjonowych funkcji lokalnych z otaczającego zakresu. Jednak wszystkie te elementy są dozwolone w wyrażeniunameof()
.
13.7 Instrukcje wyrażeń
Expression_statement oblicza dane wyrażenie. Wartość obliczona przez wyrażenie, jeśli istnieje, jest odrzucana.
expression_statement
: statement_expression ';'
;
statement_expression
: null_conditional_invocation_expression
| invocation_expression
| object_creation_expression
| assignment
| post_increment_expression
| post_decrement_expression
| pre_increment_expression
| pre_decrement_expression
| await_expression
;
Nie wszystkie wyrażenia są dozwolone jako instrukcje.
Uwaga: W szczególności wyrażenia takie jak
x + y
ix == 1
, które tylko obliczają wartość (która zostanie odrzucona), nie są dozwolone jako instrukcje. notatka końcowa
Wykonanie expression_statement oblicza zawarte wyrażenie, a następnie przenosi kontrolkę do punktu końcowego expression_statement. Punkt końcowy expression_statement jest osiągalny, jeśli expression_statement jest osiągalny.
13.8 Instrukcje wyboru
13.8.1 Ogólne
Instrukcje wyboru wybierają jedną z wielu możliwych instrukcji do wykonania na podstawie wartości pewnego wyrażenia.
selection_statement
: if_statement
| switch_statement
;
13.8.2 Instrukcja if
Instrukcja if
wybiera instrukcję do wykonania na podstawie wartości wyrażenia logicznego.
if_statement
: 'if' '(' boolean_expression ')' embedded_statement
| 'if' '(' boolean_expression ')' embedded_statement
'else' embedded_statement
;
Część else
jest skojarzona z najbliższą leksykalicznie wcześniejszą if
, która jest dozwolona przez składnię.
Przykład: w związku z
if
tym instrukcja formularzaif (x) if (y) F(); else G();
jest równoważny
if (x) { if (y) { F(); } else { G(); } }
przykład końcowy
Instrukcja if
jest wykonywana w następujący sposób:
- Ocenia się boolean_expression (§12.24).
- Jeśli wyrażenie logiczne zwróci wartość
true
, kontrolka zostanie przeniesiona do pierwszej osadzonej instrukcji. Kiedy i jeśli kontrolka osiągnie punkt końcowy tej instrukcji, kontrolka jest przenoszona do punktu końcowegoif
instrukcji. - Jeśli wyrażenie logiczne zwraca wartość
false
i jeślielse
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ńcowegoif
instrukcji. - Jeśli wyrażenie logiczne zwraca wartość
false
i jeślielse
część nie jest obecna, kontrolka jest przenoszona do punktu końcowegoif
instrukcji .
Pierwsza osadzona instrukcja if
jest osiągalna, jeśli if
instrukcja jest osiągalna, a wyrażenie logiczne nie ma stałej wartości false
.
Druga osadzona instrukcja if
, jeśli jest obecna, jest osiągalna, jeśli if
instrukcja jest osiągalna, a wyrażenie logiczne nie ma stałej wartości true
.
Punkt końcowy instrukcji if
jest osiągalny, jeśli punkt końcowy co najmniej jednego z osadzonych instrukcji jest osiągalny. Ponadto punkt końcowy instrukcji if
bez części jest else
osiągalny, jeśli if
instrukcja jest osiągalna, a wyrażenie logiczne nie ma stałej wartości true
.
13.8.3 Instrukcja switch
Instrukcja switch
wybiera do wykonania listę instrukcji o skojarzonej etykiecie przełącznika, która odpowiada wartości wyrażenia switch.
switch_statement
: 'switch' '(' expression ')' switch_block
;
switch_block
: '{' switch_section* '}'
;
switch_section
: switch_label+ statement_list
;
switch_label
: 'case' pattern case_guard? ':'
| 'default' ':'
;
case_guard
: 'when' expression
;
Switch_statement składa się ze słowa kluczowego switch
, po którym następuje wyrażenie nawiasowe (nazywane wyrażeniem przełącznika), a następnie switch_block. Switch_block składa się z zera lub większej liczby switch_sections, ujętej w nawiasy klamrowe. Każdy switch_section składa się z co najmniej jednego switch_label, po którym następuje statement_list (§13.3.2). Każda switch_label zawierająca case
ma skojarzony wzorzec (§11), względem którego jest testowana wartość wyrażenia przełącznika. Jeśli case_guard jest obecny, jego wyrażenie jest niejawnie konwertowane na typ bool
i wyrażenie to jest oceniane jako dodatkowy warunek, aby sprawa została uznana za spełnioną.
Typ zarządzający instrukcji switch
jest ustanawiany przez wyrażenie switch.
- Jeśli typem wyrażenia przełącznika jest
sbyte
, ,byte
ushort
int
long
char
bool
ulong
short
uint
string
lub enum_type, lub jeśli jest to typ wartości dopuszczających wartość null odpowiadający jednemu z tych typów, jest to typswitch
zarządzający instrukcji . - W przeciwnym razie, jeśli dokładnie jedna niejawna konwersja zdefiniowana przez użytkownika istnieje z typu wyrażenia przełącznika do jednego z następujących możliwych typów zarządzania:
sbyte
, ,byte
,int
long
ushort
short
ulong
char
uint
string
lub, typu wartości dopuszczanej do wartości null odpowiadającego jednemu z tych typów, przekonwertowany typ jest typemswitch
zarządzającym instrukcji. - W przeciwnym razie typ zarządzający instrukcji
switch
jest typem wyrażenia przełącznika. Jest to błąd, jeśli taki typ nie istnieje.
W instrukcji może znajdować się co najwyżej jedna default
etykieta switch
.
Jest to błąd, jeśli wzorzec jakiejkolwiek etykiety przełącznika nie ma zastosowania (§11.2.1) do typu wyrażenia wejściowego.
Jest to błąd, jeśli wzorzec dowolnej etykiety przełącznika jest subsumowany przez (§11.3) zestaw wzorców wcześniejszych etykiet przełącznika instrukcji switch, które nie mają osłony wielkości liter lub którego osłona wielkości liter jest stałym wyrażeniem o wartości true.
Przykład:
switch (shape) { case var x: break; case var _: // error: pattern subsumed, as previous case always matches break; default: break; // warning: unreachable, all possible values already handled. }
przykład końcowy
Instrukcja switch
jest wykonywana w następujący sposób:
- Wyrażenie przełącznika jest obliczane i konwertowane na typ zarządzający.
- Kontrolka jest przekazywana zgodnie z wartością przekonwertowanego wyrażenia przełącznika:
- Pierwszy wzorzec leksykalnie w zestawie
case
etykiet w tej samejswitch
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 dopasowanejcase
etykiecie. - W przeciwnym razie, jeśli etykieta jest obecna, kontrolka
default
zostanie przeniesiona na listę instrukcji po etykieciedefault
. - W przeciwnym razie kontrolka jest przenoszona do punktu końcowego instrukcji
switch
.
- Pierwszy wzorzec leksykalnie w zestawie
Uwaga: kolejność dopasowywania wzorców w czasie wykonywania nie jest zdefiniowana. Kompilator jest dozwolony (ale nie jest wymagany) do dopasowania wzorców poza kolejność i ponownego użycia wyników już dopasowanych wzorców w celu obliczenia wyniku dopasowania innych wzorców. Niemniej jednak kompilator jest wymagany do określenia leksykalnie pierwszego wzorca zgodnego z wyrażeniem i dla którego klauzula guard jest nieobecna lub ocenia wartość
true
. notatka końcowa
Jeśli punkt końcowy listy instrukcji sekcji przełącznika jest osiągalny, wystąpi błąd czasu kompilacji. Jest to nazywane regułą "no fall through".
Przykład: przykład
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; default: CaseOthers(); break; }
jest prawidłowa, ponieważ żadna sekcja przełącznika nie ma osiągalnego punktu końcowego. W przeciwieństwie do języka C i C++, wykonanie sekcji przełącznika nie może "przełączyć się" do następnej sekcji przełącznika i przykład
switch (i) { case 0: CaseZero(); case 1: CaseZeroOrOne(); default: CaseAny(); }
powoduje wystąpienie błędu czasu kompilacji. Po wykonaniu sekcji przełącznika należy wykonać inną sekcję przełącznika, należy użyć jawnej
goto case
instrukcji lubgoto 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:
idefault:
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ższejswitch
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
lubgoto default
, ale każda konstrukcja, która renderuje punkt końcowy listy instrukcji jest dozwolony. Na przykład instrukcja kontrolowanawhile
przez wyrażenietrue
logiczne jest znana, aby nigdy nie osiągać punktu końcowego. Podobnie instrukcja orreturn
zawsze przenosi kontrolkęthrow
gdzie indziej i nigdy nie osiąga punktu końcowego. W związku z tym następujący przykład jest prawidłowy:switch (i) { case 0: while (true) { F(); } case 1: throw new ArgumentException(); case 2: return; }
notatka końcowa
Przykład: Typ zarządzający instrukcji może być typem
switch
string
. Na przykład:void DoCommand(string command) { switch (command.ToLower()) { case "run": DoRun(); break; case "save": DoSave(); break; case "quit": DoQuit(); break; default: InvalidCommand(command); break; } }
przykład końcowy
Uwaga: Podobnie jak operatory równości ciągów (§12.12.8), instrukcja uwzględnia wielkość liter i wykonuje daną sekcję przełącznika tylko wtedy,
switch
gdy ciąg wyrażenia przełącznika dokładnie pasuje do stałejcase
etykiety. end note Jeśli typ zarządzający instrukcjiswitch
jeststring
lub typ wartości dopuszczanej do wartości null, wartośćnull
jest dozwolonacase
jako stała etykiety.
Statement_list s switch_block może zawierać oświadczenia deklaracji (§13.6). Zakres zmiennej lokalnej lub stałej zadeklarowanej w bloku przełącznika to blok przełącznika.
Etykieta przełącznika jest osiągalna, jeśli co najmniej jedna z następujących wartości jest prawdziwa:
- Wyrażenie przełącznika jest stałą wartością i albo
- etykieta jest wzorcem
case
, którego wzorzec będzie zgodny (§11.2.1) tej wartości, a ochrona etykiety jest nieobecna lub nie jest wyrażeniem stałym z wartością false; lub - jest to etykieta
default
, a żadna sekcja przełącznika nie zawiera etykiety wielkości liter, której wzorzec będzie pasował do tej wartości i którego ochrona jest nieobecna lub wyrażenie stałe z wartością true.
- etykieta jest wzorcem
- Wyrażenie przełącznika nie jest wartością stałą i albo
- etykieta
case
jest bez ochrony lub z strażnikiem, którego wartość nie jest stałą wartością false; lub - jest to etykieta
default
i- zestaw wzorców pojawiających się wśród przypadków instrukcji przełącznika, które nie mają strażników lub mają strażników, których wartość jest stałą true, nie jest wyczerpująca (§11.4) dla typu zarządzającego przełącznikiem; lub
- typ zarządzający przełącznikiem jest typem dopuszczającym wartość null i zestawem wzorców wyświetlanych wśród przypadków instrukcji switch, które nie mają osłon lub mają osłony, których wartość jest stałą true, nie zawiera wzorca, który pasuje do wartości
null
.
- etykieta
- Etykieta przełącznika jest przywoływalna przez instrukcję lub
goto default
osiągalnągoto case
.
Lista instrukcji danej sekcji przełącznika jest osiągalna, jeśli switch
instrukcja jest osiągalna, a sekcja przełącznika zawiera osiągalną etykietę przełącznika.
Punkt końcowy instrukcji switch
jest osiągalny, jeśli instrukcja switch jest osiągalna, a co najmniej jedna z następujących wartości jest prawdziwa:
- Instrukcja
switch
zawiera osiągalnąbreak
instrukcję, która kończy instrukcjęswitch
. - Etykieta nie
default
jest obecna i albo- Wyrażenie przełącznika jest wartością niestałych, a zestaw wzorców wyświetlanych wśród przypadków instrukcji switch, które nie mają osłon lub mają osłony, których wartość jest stałą true, nie jest wyczerpująca (§11.4) dla typu zarządzającego przełącznikiem.
- Wyrażenie przełącznika jest niestałych wartości typu dopuszczającego wartość null i nie występuje wzorzec wśród przypadków instrukcji switch, które nie mają strażników lub mają strażników, których wartość jest stałą true, będzie zgodna z wartością
null
. - Wyrażenie przełącznika jest stałą wartością i żadna
case
etykieta bez ochrony lub której ochrona jest stałą true, będzie zgodna z tej wartości.
Przykład: Poniższy kod przedstawia zwięzłe użycie klauzuli
when
:static object CreateShape(string shapeDescription) { switch (shapeDescription) { case "circle": return new Circle(2); … case var o when string.IsNullOrWhiteSpace(o): return null; default: return "invalid shape description"; } }
Przypadek wariancja jest zgodny
null
z ciągiem pustym lub dowolnym ciągiem zawierającym tylko białe znaki. przykład końcowy
13.9 Instrukcje iteracji
13.9.1 Ogólne
Instrukcje iteracji wielokrotnie wykonują instrukcję osadzoną.
iteration_statement
: while_statement
| do_statement
| for_statement
| foreach_statement
;
13.9.2 Instrukcja while
Instrukcja while
warunkowo wykonuje osadzoną instrukcję zero lub więcej razy.
while_statement
: 'while' '(' boolean_expression ')' embedded_statement
;
Instrukcja while
jest wykonywana w następujący sposób:
- Ocenia się boolean_expression (§12.24).
- Jeśli wyrażenie logiczne zwróci wartość
true
, kontrolka zostanie przeniesiona do instrukcji embedded. Kiedy i jeśli kontrolka osiągnie punkt końcowy osadzonej instrukcji (prawdopodobnie z wykonaniacontinue
instrukcji), kontrolka jest przenoszona na początek instrukcjiwhile
. - Jeśli wyrażenie logiczne zwraca wartość
false
, kontrolka jest przenoszona do punktu końcowego instrukcjiwhile
.
W osadzonej while
break
instrukcji instrukcja (§13.10.2) może służyć do przenoszenia kontroli do punktu while
końcowego instrukcji (w ten sposób kończąca iterację osadzonej instrukcji) i continue
instrukcji (§13.10.3) może służyć do przenoszenia kontroli do punktu końcowego osadzonej instrukcji (w związku z tym wykonywanie kolejnej iteracji while
instrukcji).
Osadzona instrukcja while
jest osiągalna, jeśli while
instrukcja jest osiągalna, a wyrażenie logiczne nie ma stałej wartości false
.
Punkt końcowy instrukcji while
jest osiągalny, jeśli spełniony jest co najmniej jeden z następujących warunków:
- Instrukcja
while
zawiera osiągalnąbreak
instrukcję, która kończy instrukcjęwhile
. - Instrukcja
while
jest osiągalna, a wyrażenie logiczne nie ma stałej wartościtrue
.
13.9.3 Instrukcja do
Instrukcja do
warunkowo wykonuje osadzoną instrukcję co najmniej raz.
do_statement
: 'do' embedded_statement 'while' '(' boolean_expression ')' ';'
;
Instrukcja do
jest wykonywana w następujący sposób:
- Kontrolka jest przenoszona do instrukcji embedded.
- Kiedy i jeśli kontrolka osiągnie punkt końcowy osadzonej instrukcji (prawdopodobnie z wykonania
continue
instrukcji), boolean_expression (§12.24) jest obliczany. Jeśli wyrażenie logiczne zwraca wartośćtrue
, kontrolka zostanie przeniesiona na początek instrukcjido
. W przeciwnym razie kontrolka jest przenoszona do punktu końcowego instrukcjido
.
W osadzonej do
break
instrukcji instrukcja (§13.10.2) może służyć do przenoszenia kontroli do punktu do
końcowego instrukcji (w ten sposób kończąca iterację osadzonej instrukcji) i continue
instrukcji (§13.10.3) może służyć do przenoszenia kontroli do punktu końcowego osadzonej instrukcji (w związku z tym wykonywanie kolejnej iteracji do
instrukcji).
Osadzona instrukcja instrukcji do
jest osiągalna, jeśli do
instrukcja jest osiągalna.
Punkt końcowy instrukcji do
jest osiągalny, jeśli spełniony jest co najmniej jeden z następujących warunków:
- Instrukcja
do
zawiera osiągalnąbreak
instrukcję, która kończy instrukcjędo
. - Punkt końcowy osadzonej instrukcji jest osiągalny, a wyrażenie logiczne nie ma stałej wartości
true
.
13.9.4 Instrukcja for
Instrukcja for
oblicza sekwencję wyrażeń inicjalizacji, a następnie, gdy warunek jest spełniony, wielokrotnie wykonuje osadzoną instrukcję i ocenia sekwencję wyrażeń iteracji.
for_statement
: 'for' '(' for_initializer? ';' for_condition? ';' for_iterator? ')'
embedded_statement
;
for_initializer
: local_variable_declaration
| statement_expression_list
;
for_condition
: boolean_expression
;
for_iterator
: statement_expression_list
;
statement_expression_list
: statement_expression (',' statement_expression)*
;
For_initializer, jeśli istnieje, składa się z local_variable_declaration (§13.6.2) lub listy statement_expression s (§13.7) oddzielonych przecinkami. Zakres zmiennej lokalnej zadeklarowanej przez for_initializer to for_initializer, for_condition, for_iterator i embedded_statement.
For_condition, jeśli istnieje, jest boolean_expression (§12.24).
For_iterator, jeśli jest obecny, składa się z listy statement_expressions (§13.7) rozdzielonych przecinkami.
Instrukcja for
jest wykonywana w następujący sposób:
- Jeśli for_initializer jest obecny, inicjatory zmiennych lub wyrażenia instrukcji są wykonywane w kolejności ich zapisu. Ten krok jest wykonywany tylko raz.
- Jeśli for_condition jest obecny, jest obliczany.
- Jeśli for_condition nie istnieje lub jeśli ocena daje wartość
true
, kontrola zostanie przeniesiona do instrukcji osadzonej. Kiedy i jeśli kontrolka osiągnie punkt końcowy osadzonej instrukcji (prawdopodobnie z wykonaniacontinue
instrukcji), wyrażenia for_iterator, jeśli istnieją, są oceniane w sekwencji, a następnie wykonywana jest kolejna iteracja, począwszy od oceny for_condition w powyższym kroku. - Jeśli for_condition jest obecny, a ocena daje wartość
false
, kontrola jest przekazywana do punktu końcowegofor
instrukcji.
W osadzonej for
break
instrukcji instrukcja (§13.10.2) może służyć do przenoszenia kontroli do punktu for
końcowego instrukcji (w ten sposób kończąca iterację osadzonej instrukcji) i continue
instrukcji (§13.10.3) może służyć do przenoszenia kontroli do punktu końcowego osadzonej instrukcji (w ten sposób wykonywania for_iterator i wykonywania innej iteracji for
instrukcji, począwszy od for_condition).
Osadzona instrukcja for
jest osiągalna, jeśli jedna z następujących wartości jest prawdziwa:
- Instrukcja
for
jest osiągalna i nie ma for_condition . - Instrukcja
for
jest osiągalna, a for_condition jest obecny i nie ma stałej wartościfalse
.
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ścitrue
.
13.9.5 Instrukcja foreach
Instrukcja foreach
wylicza elementy kolekcji, wykonując instrukcję osadzoną dla każdego elementu kolekcji.
foreach_statement
: 'foreach' '(' ref_kind? local_variable_type identifier 'in'
expression ')' embedded_statement
;
Local_variable_type i identyfikator instrukcji foreach deklarują zmienną iteracji instrukcji. var
Jeśli identyfikator jest podany jako local_variable_type, a żaden typ o nazwie var
nie jest w zakresie, zmienna iteracji jest określana jako niejawnie typ zmiennej iteracji, a jej typ jest traktowany jako typ foreach
elementu instrukcji, jak określono poniżej.
Jeśli foreach_statement zawiera zarówno, jak i ref
, readonly
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 z zakresem rozszerzającym się na instrukcję osadzoną. Podczas wykonywania instrukcji foreach
zmienna iteracji reprezentuje element kolekcji, dla którego jest obecnie wykonywana iteracja. Jeśli zmienna iteracji oznacza zmienną tylko do odczytu, występuje błąd czasu kompilacji, jeśli osadzona instrukcja próbuje zmodyfikować ją (za pośrednictwem przypisania lub ++
operatorów i --
) lub przekazać ją jako odwołanie lub parametr wyjściowy.
W poniższym celu, aby uzyskać zwięzłość, IEnumerable
, IEnumerator
IEnumerable<T>
i IEnumerator<T>
odwołaj się do odpowiednich typów w przestrzeniach System.Collections
nazw i System.Collections.Generic
.
Przetwarzanie w czasie kompilacji instrukcji foreach
najpierw określa typ kolekcji, typ modułu wyliczającego i typ iteracji wyrażenia. Ta determinacja jest kontynuowana w następujący sposób:
- Jeśli typ wyrażenia jest typem
X
tablicy, istnieje niejawna konwersja odwołania z X naIEnumerable
interfejs (ponieważSystem.Array
implementuje ten interfejs). Typ kolekcji jest interfejsem, typemIEnumerable
modułu wyliczającego jestIEnumerator
interfejs, a typ iteracji jest typem elementu typuX
tablicy . - Jeśli typ
X
wyrażenia to, istnieje niejawna konwersja z wyrażenia naIEnumerable
interfejs (§10.2.10).dynamic
Typ kolekcji jest interfejsemIEnumerable
, a typem modułuIEnumerator
wyliczającego jest interfejs.var
Jeśli identyfikator jest podany jako local_variable_type, typ iteracji todynamic
, w przeciwnym razie jestobject
to . - W przeciwnym razie określ, czy typ
X
ma odpowiedniąGetEnumerator
metodę:- Przeprowadź wyszukiwanie składowych dla typu
X
z identyfikatoremGetEnumerator
i bez argumentów typu. Jeśli wyszukiwanie elementu członkowskiego nie generuje dopasowania lub generuje niejednoznaczność lub tworzy dopasowanie, które nie jest grupą metod, sprawdź interfejs wyliczalny zgodnie z poniższym opisem. Zaleca się, aby ostrzeżenie zostało wydane, jeśli wyszukiwanie elementu członkowskiego generuje dowolne elementy, z wyjątkiem grupy metod lub braku dopasowania. - Przeprowadź rozpoznawanie przeciążeń przy użyciu wynikowej grupy metod i pustej listy argumentów. Jeśli rozpoznawanie przeciążenia nie powoduje zastosowania metod, powoduje niejednoznaczność lub powoduje utworzenie jednej najlepszej metody, ale ta metoda jest statyczna lub nie jest publiczna, sprawdź interfejs wyliczalny, jak opisano poniżej. Zaleca się, aby ostrzeżenie zostało wydane, jeśli rozwiązanie przeciążenia generuje dowolne elementy, z wyjątkiem jednoznacznej metody wystąpienia publicznego lub nie ma odpowiednich metod.
- Jeśli zwracany typ
E
GetEnumerator
metody nie jest klasą, strukturą lub typem interfejsu, zostanie wygenerowany błąd i nie zostaną podjęte żadne dalsze kroki. - Wyszukiwanie składowe jest wykonywane przy
E
użyciu identyfikatoraCurrent
i bez argumentów typu. Jeśli wyszukiwanie elementu członkowskiego nie daje dopasowania, wynik jest błędem lub wynikiem jest wszystko, z wyjątkiem właściwości wystąpienia publicznego, która zezwala na odczyt, zostanie wygenerowany błąd i nie zostaną podjęte żadne dalsze kroki. - Wyszukiwanie składowe jest wykonywane przy
E
użyciu identyfikatoraMoveNext
i bez argumentów typu. Jeśli wyszukiwanie składowe nie daje dopasowania, wynik jest błędem lub wynikiem jest coś poza grupą metod, generowany jest błąd i nie są wykonywane żadne dalsze kroki. - Rozpoznawanie przeciążenia jest wykonywane w grupie metod z pustą listą argumentów. Jeśli rozpoznawanie przeciążenia nie powoduje żadnych odpowiednich metod, powoduje niejednoznaczność lub powoduje utworzenie jednej najlepszej metody, ale ta metoda jest statyczna lub nie jest publiczna lub jej typ zwracany nie
bool
jest , zostanie wygenerowany błąd i nie zostaną wykonane żadne dalsze kroki. - Typ kolekcji to
X
, typ modułu wyliczającego toE
, a typ iteracji jest typemCurrent
właściwości. WłaściwośćCurrent
może zawieraćref
modyfikator, w którym przypadku zwracane wyrażenie jest variable_reference (§9.5), który jest opcjonalnie tylko do odczytu.
- Przeprowadź wyszukiwanie składowych dla typu
- W przeciwnym razie sprawdź interfejs wyliczalny:
- Jeśli wśród wszystkich typów
Tᵢ
, dla których istnieje niejawna konwersja zX
doIEnumerable<Tᵢ>
, istnieje unikatowy typT
, któryT
niedynamic
jest i dla wszystkich pozostałychTᵢ
istnieje niejawna konwersja zIEnumerable<T>
doIEnumerable<Tᵢ>
, typ kolekcji jest interfejs , typ wyliczający jest interfejsIEnumerable<T>
IEnumerator<T>
, a typ iteracji toT
. - W przeciwnym razie, jeśli istnieje więcej niż jeden taki typ
T
, zostanie wygenerowany błąd i nie zostaną wykonane żadne dalsze kroki. - W przeciwnym razie, jeśli istnieje niejawna konwersja z do interfejsu
System.Collections.IEnumerable
, typ kolekcji jest tym interfejsem, typ modułu wyliczającego to interfejsSystem.Collections.IEnumerator
, a typ iteracji toobject
.X
- W przeciwnym razie zostanie wygenerowany błąd i nie zostaną podjęte żadne dalsze kroki.
- Jeśli wśród wszystkich typów
Powyższe kroki, jeśli zakończyły się pomyślnie, jednoznacznie tworzą typ kolekcji , typ C
modułu wyliczającego i typ E
T
iteracji , ref T
lub ref readonly T
. Instrukcja foreach
formularza
foreach (V v in x) «embedded_statement»
element jest wówczas odpowiednikiem:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
V v = (V)(T)e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
Zmienna e
nie jest widoczna ani dostępna dla wyrażenia x
, instrukcji osadzonej ani żadnego innego kodu źródłowego programu. Zmienna v
jest tylko do odczytu w instrukcji embedded. Jeśli nie ma jawnej konwersji (§10.3) z T
(typu iteracji) do V
( local_variable_type w foreach
instrukcji), zostanie wygenerowany błąd i nie zostaną wykonane żadne dalsze kroki.
Gdy zmienna iteracji jest zmienną referencyjną (§9.7), foreach
instrukcja formularza
foreach (ref V v in x) «embedded_statement»
element jest wówczas odpowiednikiem:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
ref V v = ref e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
Zmienna e
nie jest widoczna ani dostępna dla wyrażenia x
, instrukcji osadzonej lub innego kodu źródłowego programu. Zmienna v
referencyjna jest zapisem w osadzonej instrukcji, ale v
nie jest ponownie przypisana (§12.21.3). Jeśli nie istnieje konwersja tożsamości (§10.2.2) z T
(typ iteracji) na V
( local_variable_type w foreach
instrukcji), zostanie wygenerowany błąd i nie zostaną wykonane żadne dalsze kroki.
foreach
Instrukcja formularza foreach (ref readonly V v in x) «embedded_statement»
ma podobną równoważną formę, ale zmienna v
referencyjna znajduje ref readonly
się w instrukcji osadzonej i w związku z tym nie może zostać ponownie przypisana ani ponownie przypisana.
Uwaga: jeśli
x
ma wartośćnull
,System.NullReferenceException
element jest zgłaszany w czasie wykonywania. notatka końcowa
Implementacja jest dozwolona do implementacji danego foreach_statement inaczej, np. ze względów wydajności, o ile zachowanie jest zgodne z powyższym rozszerzeniem.
Umieszczenie v
wewnątrz while
pętli jest ważne dla sposobu przechwytywania (§12.19.6.2) przez dowolną funkcję anonimową występującą w embedded_statement.
Przykład:
int[] values = { 7, 9, 13 }; Action f = null; foreach (var value in values) { if (f == null) { f = () => Console.WriteLine("First value: " + value); } } f();
Jeśli
v
w formularzu rozwiniętym został zadeklarowany poza pętląwhile
, będzie on współużytkowany wśród wszystkich iteracji, a jego wartość pofor
pętli będzie wartością końcową ,13
czyli tym, co wywołanief
zostanie wydrukowane. Zamiast tego, ponieważ każda iteracja ma własną zmiennąv
, ta przechwyconaf
w pierwszej iteracji będzie nadal przechowywać wartość7
, która będzie drukowana. (Należy pamiętać, że wcześniejsze wersje języka C# zadeklarowanev
poza pętląwhile
).przykład końcowy
Treść finally
bloku jest skonstruowana zgodnie z następującymi krokami:
Jeśli istnieje niejawna konwersja z
E
do interfejsu, wówczasSystem.IDisposable
Jeśli
E
jest typem wartości innej niż null, klauzulafinally
zostanie rozszerzona do semantycznego odpowiednika:finally { ((System.IDisposable)e).Dispose(); }
W przeciwnym razie klauzula
finally
jest rozszerzana na semantyczny odpowiednik:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }
z wyjątkiem tego, że jeśli
E
jest typem wartości lub parametrem typu utworzonego w typie wartości, konwersja parametrue
naSystem.IDisposable
nie powoduje wystąpienia pola.
W przeciwnym razie, jeśli
E
jest typem zapieczętowanym, klauzulafinally
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 jakoint
, typ iteracji .numbers
przykład końcowy
13.10 Instrukcje Jump
13.10.1 Ogólne
Skocz oświadczenia bezwarunkowo kontroli transferu.
jump_statement
: break_statement
| continue_statement
| goto_statement
| return_statement
| throw_statement
;
Lokalizacja, do której kontrolka przeskoku transferuje instrukcję, jest nazywana elementem docelowym instrukcji skoku.
Gdy instrukcja skoku występuje w bloku, a celem tej instrukcji skoku jest poza tym blokiem, instrukcja jump mówi się, aby zamknąć blok. Podczas gdy instrukcja skoku może przenosić kontrolę z bloku, nigdy nie może przenieść kontroli do bloku.
Wykonywanie instrukcji skoku jest skomplikowane dzięki obecności interweniujących try
instrukcji. W przypadku braku takich try
stwierdzeń, oświadczenie skoku bezwarunkowo przenosi kontrolę z instrukcji jump do celu. W obecności takich interweniujących try
instrukcji wykonywanie jest bardziej złożone. Jeśli instrukcja jump kończy co najmniej jeden try
blok ze skojarzonymi finally
blokami, kontrolka jest początkowo przenoszona do finally
bloku najbardziej wewnętrznej try
instrukcji. Gdy kontrolka i jeśli osiągnie punkt finally
końcowy bloku, kontrolka zostanie przeniesiona do finally
bloku następnej instrukcji otaczającej try
. Ten proces jest powtarzany do momentu finally
wykonania bloków wszystkich interweniujących try
instrukcji.
Przykład: w poniższym kodzie
class Test { static void Main() { while (true) { try { try { Console.WriteLine("Before break"); break; } finally { Console.WriteLine("Innermost finally block"); } } finally { Console.WriteLine("Outermost finally block"); } } Console.WriteLine("After break"); } }
finally
bloki skojarzone z dwiematry
instrukcjami są wykonywane przed przeniesieniem kontrolki do obiektu docelowego instrukcji jump. Wygenerowane dane wyjściowe są następujące:Before break Innermost finally block Outermost finally block After break
przykład końcowy
13.10.2 Instrukcja break
Instrukcja break
zamyka najbliższą otaczającą switch
instrukcję , while
, do
, for
, lub foreach
.
break_statement
: 'break' ';'
;
Elementem docelowym instrukcji break
jest punkt końcowy najbliższej otaczającej switch
instrukcji , while
, do
, for
, lub foreach
. break
Jeśli instrukcja nie jest ujęta w instrukcję switch
, while
, do
, for
lubforeach
, wystąpi błąd czasu kompilacji.
Gdy wiele switch
instrukcji , , do
while
, for
lub jest foreach
zagnieżdżonych między sobą, break
instrukcja ma zastosowanie tylko do najbardziej wewnętrznej instrukcji. Aby przenieść kontrolę na wiele poziomów zagnieżdżania, goto
należy użyć instrukcji (§13.10.4).
Instrukcja break
nie może zamknąć finally
bloku (§13.11). break
Gdy instrukcja występuje w finally
bloku, element docelowy instrukcji break
mieści się w tym samym finally
bloku; w przeciwnym razie wystąpi błąd czasu kompilacji.
Instrukcja break
jest wykonywana w następujący sposób:
break
Jeśli instrukcja kończy co najmniej jedentry
blok ze skojarzonymifinally
blokami, kontrolka jest początkowo przenoszona dofinally
bloku najbardziej wewnętrznejtry
instrukcji. Gdy kontrolka i jeśli osiągnie punktfinally
końcowy bloku, kontrolka zostanie przeniesiona dofinally
bloku następnej instrukcji otaczającejtry
. Ten proces jest powtarzany do momentufinally
wykonania bloków wszystkich interweniującychtry
instrukcji.- Kontrolka jest przenoszona do elementu docelowego instrukcji
break
.
break
Ponieważ oświadczenie bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji break
nigdy nie jest osiągalny.
13.10.3 Instrukcja continue
Instrukcja continue
rozpoczyna nową iterację najbliższej otaczającej while
instrukcji , do
, for
lub foreach
.
continue_statement
: 'continue' ';'
;
Elementem docelowym instrukcji continue
jest punkt końcowy osadzonej instrukcji najbliższej otaczającej while
instrukcji , do
, for
lub foreach
. continue
Jeśli instrukcja nie jest ujęta w instrukcję while
, do
, for
lubforeach
, wystąpi błąd czasu kompilacji.
Gdy wiele while
instrukcji , do
, for
lub jest foreach
zagnieżdżonych w sobie, continue
instrukcja ma zastosowanie tylko do najbardziej wewnętrznej instrukcji. Aby przenieść kontrolę na wiele poziomów zagnieżdżania, goto
należy użyć instrukcji (§13.10.4).
Instrukcja continue
nie może zamknąć finally
bloku (§13.11). continue
Gdy instrukcja występuje w finally
bloku, element docelowy instrukcji continue
mieści się w tym samym finally
bloku; w przeciwnym razie wystąpi błąd czasu kompilacji.
Instrukcja continue
jest wykonywana w następujący sposób:
continue
Jeśli instrukcja kończy co najmniej jedentry
blok ze skojarzonymifinally
blokami, kontrolka jest początkowo przenoszona dofinally
bloku najbardziej wewnętrznejtry
instrukcji. Gdy kontrolka i jeśli osiągnie punktfinally
końcowy bloku, kontrolka zostanie przeniesiona dofinally
bloku następnej instrukcji otaczającejtry
. Ten proces jest powtarzany do momentufinally
wykonania bloków wszystkich interweniującychtry
instrukcji.- Kontrolka jest przenoszona do elementu docelowego instrukcji
continue
.
continue
Ponieważ oświadczenie bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji continue
nigdy nie jest osiągalny.
13.10.4 Instrukcja goto
Instrukcja goto
przenosi kontrolkę do instrukcji oznaczonej etykietą.
goto_statement
: 'goto' identifier ';'
| 'goto' 'case' constant_expression ';'
| 'goto' 'default' ';'
;
Elementem docelowym instrukcji goto
identyfikatora jest instrukcja oznaczona etykietą z daną etykietą. Jeśli etykieta o podanej nazwie nie istnieje w bieżącym elemencie członkowskim funkcji lub jeśli goto
instrukcja nie znajduje się w zakresie etykiety, wystąpi błąd czasu kompilacji.
Uwaga: Ta reguła zezwala na używanie
goto
instrukcji do przenoszenia kontroli poza zagnieżdżony zakres, ale nie do zagnieżdżonego zakresu. W przykładzieclass Test { static void Main(string[] args) { string[,] table = { {"Red", "Blue", "Green"}, {"Monday", "Wednesday", "Friday"} }; foreach (string str in args) { int row, colm; for (row = 0; row <= 1; ++row) { for (colm = 0; colm <= 2; ++colm) { if (str == table[row,colm]) { goto done; } } } Console.WriteLine($"{str} not found"); continue; done: Console.WriteLine($"Found {str} at [{row}][{colm}]"); } } }
instrukcja
goto
służy do przenoszenia kontroli poza zagnieżdżony zakres.notatka końcowa
Elementem docelowym instrukcji goto case
jest lista instrukcji w natychmiast otaczającej switch
instrukcji (§13.8.3), która zawiera etykietę ze stałym wzorcem case
podanej stałej wartości i bez ochrony. goto case
Jeśli instrukcja nie jest ujęta w switch
instrukcję, jeśli najbliższa instrukcja otaczająca switch
nie zawiera takiej case
instrukcji lub jeśli constant_expression nie jest niejawnie konwertowana (§10.2) do typu rządzącego najbliższej otaczającej switch
instrukcji, wystąpi błąd czasu kompilacji.
Elementem docelowym instrukcji goto default
jest lista instrukcji w natychmiast otaczającej switch
instrukcji (§13.8.3), która zawiera etykietę default
. goto default
Jeśli instrukcja nie jest ujęta w instrukcję switch
lub gdy najbliższa instrukcja otaczania switch
nie zawiera default
etykiety, wystąpi błąd czasu kompilacji.
Instrukcja goto
nie może zamknąć finally
bloku (§13.11). goto
Gdy instrukcja występuje w blokufinally
, element docelowy goto
instrukcji mieści się w tym samym finally
bloku lub w przeciwnym razie wystąpi błąd czasu kompilacji.
Instrukcja goto
jest wykonywana w następujący sposób:
goto
Jeśli instrukcja kończy co najmniej jedentry
blok ze skojarzonymifinally
blokami, kontrolka jest początkowo przenoszona dofinally
bloku najbardziej wewnętrznejtry
instrukcji. Gdy kontrolka i jeśli osiągnie punktfinally
końcowy bloku, kontrolka zostanie przeniesiona dofinally
bloku następnej instrukcji otaczającejtry
. Ten proces jest powtarzany do momentufinally
wykonania bloków wszystkich interweniującychtry
instrukcji.- Kontrolka jest przenoszona do elementu docelowego instrukcji
goto
.
goto
Ponieważ oświadczenie bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji goto
nigdy nie jest osiągalny.
13.10.5 Instrukcja return
Instrukcja return
zwraca kontrolkę do bieżącego obiektu wywołującego element członkowski funkcji, w którym pojawia się instrukcja return, opcjonalnie zwracając wartość lub variable_reference (§9.5).
return_statement
: 'return' ';'
| 'return' expression ';'
| 'return' 'ref' variable_reference ';'
;
Return_statement bez wyrażenia jest nazywana zwracaną wartością; jedno zawierające ref
wyrażenie jest nazywane wyrażeniem return-by-ref, a jedno zawierające tylko wyrażenie jest nazywane wyrażeniem return-by-value.
Jest to błąd czasu kompilacji, aby użyć zwracanej wartości z metody zadeklarowanej jako zwracane przez wartość lub zwracane przez ref (§15.6.1).
Jest to błąd czasu kompilacji, aby użyć metody return-by-ref zadeklarowanej jako return-no-value lub return-by-value.
Jest to błąd czasu kompilacji, aby użyć zwracanej przez wartość z metody zadeklarowanej jako zwracana wartość-no-value lub return-by-ref.
Jest to błąd czasu kompilacji, aby użyć wyrażenia return-by-ref, jeśli wyrażenie nie jest variable_reference lub jest odwołaniem do zmiennej, której kontekst ref-safe-context nie jest kontekstem wywołującym (§9.7.2).
Jest to błąd czasu kompilacji, aby użyć metody return-by-ref zadeklarowanej przy użyciu method_modifier async
.
Mówi się, że element członkowski funkcji oblicza wartość, jeśli jest metodą metodą zwracaną przez wartość (§15.6.11), metoda zwracana po wartości uzyskuje metodę dostępu do właściwości lub indeksatora lub operatora zdefiniowanego przez użytkownika. Elementy członkowskie funkcji, które są zwracane bez wartości, nie obliczają wartości i są metodami o efektywnym typie void
zwrotnym, ustawiać metody dostępu właściwości i indeksatorów, dodawać i usuwać metody dostępu do zdarzeń, konstruktorów wystąpień, konstruktorów statycznych i finalizatorów. Elementy członkowskie funkcji, które są zwracane przez ref, nie obliczają wartości.
W przypadku zwracanej wartości niejawna konwersja (§10.2) istnieje z typu wyrażenia do efektywnego typu zwracanego typu (§15.6.11) zawierającego elementu członkowskiego funkcji. W przypadku zwrotu po ref konwersja tożsamości (§10.2.2) istnieje między typem wyrażenia a skutecznym typem zwracanym elementu członkowskiego funkcji zawierającej.
return
instrukcje mogą być również używane w treści wyrażeń funkcji anonimowych (§12.19) i uczestniczyć w określaniu, które konwersje istnieją dla tych funkcji (§10.7.1).
Jest to błąd czasu kompilacji instrukcji return
wyświetlanej finally
w bloku (§13.11).
Instrukcja return
jest wykonywana w następujący sposób:
- W przypadku wyrażenia zwracanego według wartości wyrażenie jest obliczane, a jego wartość jest konwertowana na efektywny typ zwracany funkcji zawierającej przez niejawną konwersję. Wynik konwersji staje się wartością wynikową wygenerowaną przez funkcję. W przypadku wyrażenia return-by-ref wyrażenie jest obliczane, a wynik jest klasyfikowany jako zmienna. Jeśli element return-by-ref metody otaczającej zawiera
readonly
zmienną wynikową jest tylko do odczytu. return
Jeśli instrukcja jest ujęta przez jeden lubcatch
więcejtry
bloków ze skojarzonymifinally
blokami, kontrolka jest początkowo przenoszona dofinally
bloku najbardziej wewnętrznejtry
instrukcji. Gdy kontrolka i jeśli osiągnie punktfinally
końcowy bloku, kontrolka zostanie przeniesiona dofinally
bloku następnej instrukcji otaczającejtry
. Ten proces jest powtarzany do momentufinally
wykonania bloków wszystkich ujęćtry
instrukcji.- Jeśli funkcja zawierająca nie jest funkcją asynchroniową, kontrolka jest zwracana do obiektu wywołującego funkcji zawierającej wraz z wartością wyniku, jeśli istnieje.
- Jeśli funkcja zawierająca jest funkcją asynchroniową, kontrolka jest zwracana do bieżącego wywołującego, a wartość wyniku, jeśli istnieje, jest rejestrowana w zadaniu zwrotnym zgodnie z opisem w sekcji (§15.15.3).
return
Ponieważ oświadczenie bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji return
nigdy nie jest osiągalny.
13.10.6 Instrukcja throw
Instrukcja throw
zgłasza wyjątek.
throw_statement
: 'throw' expression? ';'
;
Instrukcja throw
z wyrażeniem zgłasza wyjątek wygenerowany przez ocenę wyrażenia. Wyrażenie jest niejawnie konwertowane na System.Exception
, a wynik oceny wyrażenia jest konwertowany na System.Exception
przed zgłoszeniem. Jeśli wynikiem konwersji jest null
, System.NullReferenceException
zamiast tego zostanie zgłoszony element .
Instrukcja throw
bez wyrażenia może być używana tylko w catch
bloku, w takim przypadku instrukcja ta ponownie zgłasza wyjątek, który jest obecnie obsługiwany przez ten catch
blok.
throw
Ponieważ oświadczenie bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji throw
nigdy nie jest osiągalny.
Gdy zgłaszany jest wyjątek, kontrolka jest przekazywana do pierwszej catch
klauzuli w otaczającej try
instrukcji, która może obsłużyć wyjątek. Proces, który odbywa się od momentu zgłoszenia wyjątku do punktu przenoszenia kontroli do odpowiedniego programu obsługi wyjątków, jest znany jako propagacja wyjątków. Propagacja wyjątku polega na wielokrotnej ocenie poniższych kroków do momentu catch
znalezienia klauzuli zgodnej z wyjątkiem. W tym opisie punkt zgłaszania jest początkowo lokalizacją, w której zgłaszany jest wyjątek. To zachowanie jest określone w pliku (§21.4).
W bieżącym elemencie członkowskim funkcji każda
try
instrukcja, która otacza punkt rzutu, jest badana. Dla każdej instrukcji , począwszy od najbardziej wewnętrznejtry
instrukcjiS
i kończącej się najbardziej zewnętrznątry
instrukcją, oceniane są następujące kroki:try
Jeśli blok ujętego w punkt rzutuS
i jeśliS
ma co najmniej jednącatch
klauzulę,catch
klauzule są badane w kolejności wyglądu, aby zlokalizować odpowiednią procedurę obsługi dla wyjątku. Pierwszacatch
klauzula określająca typT
wyjątku (lub parametr typu, który w czasie wykonywania określa typ wyjątkuE
), taki, że typT
czasu wykonywania pochodzi zT
, jest uważany za dopasowanie. Jeśli klauzula zawiera filtr wyjątku, obiekt wyjątku jest przypisywany do zmiennej wyjątku, a filtr wyjątku jest obliczany. Jeśli klauzulacatch
zawiera filtr wyjątku, ta klauzula jest traktowana jako zgodna,catch
jeśli filtr wyjątkutrue
zwróci wartość . Klauzula ogólnacatch
(§13.11) jest uważana za zgodną z dowolnym typem wyjątku. Jeśli znajduje się klauzula dopasowaniacatch
, propagacja wyjątku jest zakończona przez przeniesienie kontroli do bloku tejcatch
klauzuli.- W przeciwnym razie, jeśli
try
blok lubcatch
blokS
otacza punkt rzutu i jeśliS
mafinally
blok, kontrolka jest przenoszona dofinally
bloku.finally
Jeśli blok zgłasza inny wyjątek, przetwarzanie bieżącego wyjątku zostanie zakończone. W przeciwnym razie, gdy kontrolka osiągnie punktfinally
końcowy bloku, przetwarzanie bieżącego wyjątku jest kontynuowane.
Jeśli program obsługi wyjątków nie znajduje się w wywołaniu bieżącej funkcji, wywołanie funkcji zostanie zakończone i wystąpi jeden z następujących:
Jeśli bieżąca funkcja nie jest asynchroniczna, powyższe kroki są powtarzane dla obiektu wywołującego funkcji z punktem throw odpowiadającym instrukcji, z której wywoływano element członkowski funkcji.
Jeśli bieżąca funkcja jest asynchronizna i zwracana przez zadanie, wyjątek jest rejestrowany w zadaniu zwrotnym, który jest umieszczany w stanie błędnym lub anulowanym zgodnie z opisem w §15.15.3.
Jeśli bieżąca funkcja jest async i
void
-returning, kontekst synchronizacji bieżącego wątku jest powiadamiany zgodnie z opisem w §15.15.4.
Jeśli przetwarzanie wyjątku kończy wszystkie wywołania elementów członkowskich funkcji w bieżącym wątku, wskazując, że wątek nie ma procedury obsługi dla wyjątku, to wątek zostanie zakończony. Wpływ takiego zakończenia jest definiowany przez implementację.
13.11 Instrukcja try
Instrukcja try
zawiera mechanizm przechwytywania wyjątków występujących podczas wykonywania bloku. Ponadto instrukcja try
zapewnia możliwość określenia bloku kodu, który jest zawsze wykonywany, gdy kontrolka opuszcza instrukcję try
.
try_statement
: 'try' block catch_clauses
| 'try' block catch_clauses? finally_clause
;
catch_clauses
: specific_catch_clause+
| specific_catch_clause* general_catch_clause
;
specific_catch_clause
: 'catch' exception_specifier exception_filter? block
| 'catch' exception_filter block
;
exception_specifier
: '(' type identifier? ')'
;
exception_filter
: 'when' '(' boolean_expression ')'
;
general_catch_clause
: 'catch' block
;
finally_clause
: 'finally' block
;
Try_statement składa się ze słowa kluczowegotry
, po którym następuje blok, a następnie zero lub więcej catch_clauses, a następnie opcjonalny finally_clause. Istnieje co najmniej jedna catch_clause lub finally_clause.
W exception_specifier typ lub jego efektywna klasa bazowa, jeśli jest type_parameter, musi być System.Exception
lub typ, który pochodzi od niego.
Gdy klauzula catch
określa zarówno class_type , jak i identyfikator, zadeklarowana jest zmienna wyjątku podanej nazwy i typu. Zmienna wyjątku jest wprowadzana do przestrzeni deklaracji specific_catch_clause (§7.3). Podczas wykonywania exception_filter i catch
bloku zmienna wyjątku reprezentuje obecnie obsługiwany wyjątek. Do celów określonego sprawdzania przypisania zmienna wyjątku jest uznawana za zdecydowanie przypisaną w całym zakresie.
Jeśli klauzula catch
nie zawiera nazwy zmiennej wyjątku, nie można uzyskać dostępu do obiektu wyjątku w filtrze i catch
bloku.
Klauzula catch
określająca ani typ wyjątku, ani nazwę zmiennej wyjątku, jest nazywana klauzulą ogólną catch
. Oświadczenie try
może mieć tylko jedną klauzulę ogólną catch
, a jeśli istnieje, jest to ostatnia catch
klauzula.
Uwaga: Niektóre języki programowania mogą obsługiwać wyjątki, które nie są reprezentowane jako obiekt pochodzący z
System.Exception
klasy , chociaż takie wyjątki nigdy nie mogą być generowane przez kod języka C#. Klauzula ogólnacatch
może służyć do przechwytywania takich wyjątków. W związku z tym klauzula ogólnacatch
różni się semantycznie od tej, która określa typSystem.Exception
, w którym były może również przechwytywać wyjątki z innych języków. notatka końcowa
Aby zlokalizować procedurę obsługi dla wyjątku, catch
klauzule są badane w kolejności leksykalnej. Jeśli klauzula catch
określa typ, ale nie filtr wyjątku, jest to błąd czasu kompilacji dla późniejszej catch
klauzuli tej samej try
instrukcji, aby określić typ, który jest taki sam, jak lub pochodzi od tego typu.
Uwaga: bez tego ograniczenia możliwe byłoby zapisanie klauzul niemożliwych do
catch
osiągnięcia. notatka końcowa
catch
W bloku throw
instrukcja (§13.10.6) bez wyrażenia nie może służyć do ponownego zgłoszenia wyjątku przechwyconego catch
przez blok. Przypisania do zmiennej wyjątku nie zmieniają wyjątku, który jest zgłaszany ponownie.
Przykład: w poniższym kodzie
class Test { static void F() { try { G(); } catch (Exception e) { Console.WriteLine("Exception in F: " + e.Message); e = new Exception("F"); throw; // re-throw } } static void G() => throw new Exception("G"); static void Main() { try { F(); } catch (Exception e) { Console.WriteLine("Exception in Main: " + e.Message); } } }
metoda
F
przechwytuje wyjątek, zapisuje pewne informacje diagnostyczne w konsoli, zmienia zmienną wyjątku i ponownie zgłasza wyjątek. Wyjątek, który jest zgłaszany ponownie, jest oryginalnym wyjątkiem, więc generowane dane wyjściowe to:Exception in F: G Exception in Main: G
Gdyby pierwszy
catch
blok został zgłoszonye
zamiast ponownie wywrócić bieżący wyjątek, wygenerowane dane wyjściowe będą wyglądać następująco:Exception in F: G Exception in Main: F
przykład końcowy
Jest to błąd czasu kompilacji dla break
instrukcji , continue
lub goto
w celu przeniesienia kontroli z finally
bloku. break
Gdy instrukcja , continue
lub goto
występuje w finally
bloku, element docelowy instrukcji mieści się w tym samym finally
bloku lub w przeciwnym razie wystąpi błąd czasu kompilacji.
Jest to błąd czasu kompilacji dla return
instrukcji, która ma wystąpić w finally
bloku.
Gdy wykonanie osiągnie instrukcję, kontrolka try
zostanie przeniesiona try
do bloku. Jeśli kontrolka osiągnie punkt try
końcowy bloku bez propagacji wyjątku, kontrolka zostanie przeniesiona do finally
bloku, jeśli istnieje. Jeśli żaden blok nie finally
istnieje, kontrolka zostanie przeniesiona do punktu końcowego instrukcji try
.
Jeśli rozpropagowano wyjątek, klauzule, catch
jeśli istnieją, są badane w kolejności leksykalnej, szukając pierwszego dopasowania dla wyjątku. Wyszukiwanie klauzuli dopasowania catch
jest kontynuowane ze wszystkimi otaczającymi blokami zgodnie z opisem w §13.10.6. Klauzula catch
jest zgodna, jeśli typ wyjątku pasuje do dowolnego exception_specifier , a wszystkie exception_filter są prawdziwe. Klauzula catch
bez exception_specifier pasuje do dowolnego typu wyjątku. Typ wyjątku jest zgodny z exception_specifier, gdy exception_specifier określa typ wyjątku lub podstawowy typ wyjątku. Jeśli klauzula zawiera filtr wyjątku, obiekt wyjątku jest przypisywany do zmiennej wyjątku, a filtr wyjątku jest obliczany.
Jeśli został rozpropagowany wyjątek i zostanie znaleziona zgodna catch
klauzula, kontrolka zostanie przeniesiona do pierwszego pasującego catch
bloku. Jeśli kontrolka osiągnie punkt catch
końcowy bloku bez propagacji wyjątku, kontrolka zostanie przeniesiona do finally
bloku, jeśli istnieje. Jeśli żaden blok nie finally
istnieje, kontrolka zostanie przeniesiona do punktu końcowego instrukcji try
. Jeśli wyjątek został rozpropagowany z catch
bloku, kontrolka przenosi do finally
bloku, jeśli istnieje. Wyjątek jest propagowany do następnej instrukcji otaczającej try
.
Jeśli został rozpropagowany wyjątek i nie zostanie znaleziona żadna zgodna catch
klauzula, kontrolka zostanie przeniesiona do finally
bloku, jeśli istnieje. Wyjątek jest propagowany do następnej instrukcji otaczającej try
.
Instrukcje finally
bloku są zawsze wykonywane, gdy kontrolka opuszcza instrukcję try
. Jest to prawdą, czy transfer kontrolny występuje w wyniku normalnego wykonywania, w wyniku wykonania break
instrukcji , continue
, goto
, lub return
w wyniku propagacji wyjątku z instrukcji try
. Jeśli kontrolka osiągnie punkt finally
końcowy bloku bez propagacji wyjątku, kontrolka zostanie przeniesiona do punktu końcowego instrukcji try
.
Jeśli wyjątek jest zgłaszany podczas wykonywania finally
bloku i nie zostanie przechwycony w obrębie tego samego finally
bloku, wyjątek jest propagowany do następnej instrukcji otaczającej try
. Jeśli inny wyjątek był w trakcie propagacji, ten wyjątek zostanie utracony. Proces propagowania wyjątku został omówiony bardziej szczegółowo w opisie throw
instrukcji (§13.10.6).
Przykład: w poniższym kodzie
public class Test { static void Main() { try { Method(); } catch (Exception ex) when (ExceptionFilter(ex)) { Console.WriteLine("Catch"); } bool ExceptionFilter(Exception ex) { Console.WriteLine("Filter"); return true; } } static void Method() { try { throw new ArgumentException(); } finally { Console.WriteLine("Finally"); } } }
metoda
Method
zgłasza wyjątek. Pierwszą akcją jest sprawdzenie ujęćcatch
klauzul, wykonując filtry wyjątków. Następnie klauzulafinally
wMethod
pliku jest wykonywana przed przeniesieniem kontrolki do otaczającej klauzuli dopasowaniacatch
. Wynikowe dane wyjściowe to:Filter Finally Catch
przykład końcowy
Blok try
instrukcji try
jest osiągalny, jeśli try
instrukcja jest osiągalna.
Blok catch
instrukcji try
jest osiągalny, jeśli try
instrukcja jest osiągalna.
Blok finally
instrukcji try
jest osiągalny, jeśli try
instrukcja jest osiągalna.
Punkt końcowy instrukcji try
jest osiągalny, jeśli oba z następujących stwierdzeń są prawdziwe:
- Punkt
try
końcowy bloku jest osiągalny lub punkt końcowy co najmniej jednegocatch
bloku jest osiągalny. finally
Jeśli blok jest obecny, punktfinally
końcowy bloku jest osiągalny.
13.12 Zaznaczone i niezaznaczone instrukcje
Instrukcje checked
i unchecked
służą do kontrolowania kontekstu sprawdzania przepełnienia dla operacji arytmetycznych i konwersji typu całkowitego.
checked_statement
: 'checked' block
;
unchecked_statement
: 'unchecked' block
;
Instrukcja checked
powoduje, że wszystkie wyrażenia w bloku mają być oceniane w kontekście sprawdzonym, a unchecked
instrukcja powoduje, że wszystkie wyrażenia w bloku mają być oceniane w nieznakowanym kontekście.
Instrukcje checked
i unchecked
są dokładnie równoważne checked
operatorom i unchecked
(§12.8.20), z tą różnicą, że działają na blokach zamiast wyrażeń.
13.13 Instrukcja lock
Instrukcja lock
uzyskuje blokadę wzajemnego wykluczania dla danego obiektu, wykonuje instrukcję, a następnie zwalnia blokadę.
lock_statement
: 'lock' '(' expression ')' embedded_statement
;
Wyrażenie instrukcji lock
oznacza wartość typu znanego jako odwołanie. Nie jest wykonywana niejawna konwersja boksu (§10.2.9) dla wyrażenia lock
instrukcji, a zatem jest to błąd czasu kompilacji dla wyrażenia, aby oznaczyć wartość value_type.
Instrukcja lock
formularza
lock (x)
…
gdzie x
jest wyrażeniem reference_type, jest dokładnie równoważne:
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(x, ref __lockWasTaken);
...
}
finally
{
if (__lockWasTaken)
{
System.Threading.Monitor.Exit(x);
}
}
z wyjątkiem tego, że x
jest obliczany tylko raz.
Chociaż blokada wzajemnego wykluczania jest przechowywana, kod wykonywany w tym samym wątku wykonywania może również uzyskać i zwolnić blokadę. Jednak kod wykonywany w innych wątkach nie może uzyskać blokady do momentu zwolnienia blokady.
13.14 Instrukcja using
Instrukcja using
uzyskuje co najmniej jeden zasób, wykonuje instrukcję, a następnie usuwa zasób.
using_statement
: 'using' '(' resource_acquisition ')' embedded_statement
;
resource_acquisition
: local_variable_declaration
| expression
;
Zasób to klasa lub struktura, która implementuje System.IDisposable
interfejs, który zawiera pojedynczą metodę bez parametrów o nazwie .Dispose
Kod używający zasobu może wywołać Dispose
polecenie , aby wskazać, że zasób nie jest już potrzebny.
Jeśli forma resource_acquisition jest local_variable_declaration, typ local_variable_declaration musi być albo dynamic
typ, który można niejawnie przekonwertować na System.IDisposable
. Jeśli forma resource_acquisition jest wyrażeniem, wyrażenie to jest niejawnie konwertowane na System.IDisposable
.
Zmienne lokalne zadeklarowane w resource_acquisition są tylko do odczytu i zawierają inicjator. Błąd czasu kompilacji występuje, jeśli osadzona instrukcja próbuje zmodyfikować te zmienne lokalne (za pomocą przypisania lub ++
operatorów i --
), podjąć adresy lub przekazać je jako parametry referencyjne lub wyjściowe.
Instrukcja using
jest tłumaczona na trzy części: pozyskiwanie, użycie i usuwanie. Użycie zasobu jest niejawnie ujęte w instrukcję try
zawierającą klauzulę finally
. Ta finally
klauzula usuwa zasób. null
Jeśli zasób zostanie pozyskany, nie zostanie wykonane żadne wywołanie Dispose
i nie zostanie zgłoszony żaden wyjątek. Jeśli zasób jest typu dynamic
, jest on dynamicznie konwertowany przez niejawną konwersję dynamiczną (§10.2.10) na IDisposable
podczas pozyskiwania w celu zapewnienia pomyślnej konwersji przed użyciem i likwidacją.
Instrukcja using
formularza
using (ResourceType resource = «expression» ) «statement»
odpowiada jednemu z trzech możliwych rozszerzeń. Jeśli ResourceType
jest typem wartości innej niż null lub parametrem typu z ograniczeniem typu wartości (§15.2.5), rozszerzenie jest semantycznie równoważne
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
((IDisposable)resource).Dispose();
}
}
z wyjątkiem tego, że rzut do resource
System.IDisposable
nie powoduje wystąpienia boksu.
W przeciwnym razie, gdy ResourceType
ma dynamic
wartość , rozszerzenie jest
{
ResourceType resource = «expression»;
IDisposable d = resource;
try
{
«statement»;
}
finally
{
if (d != null)
{
d.Dispose();
}
}
}
W przeciwnym razie rozszerzenie jest
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IDisposable d = (IDisposable)resource;
if (d != null)
{
d.Dispose();
}
}
}
W każdym rozszerzeniu zmienna resource
jest tylko do odczytu w instrukcji osadzonej, a d
zmienna jest niedostępna i niewidoczna dla osadzonej instrukcji.
Implementacja może implementować daną using_statement inaczej, np. ze względów wydajności, o ile zachowanie jest zgodne z powyższym rozszerzeniem.
Instrukcja using
formularza:
using («expression») «statement»
ma te same trzy możliwe rozszerzenia. W tym przypadku ResourceType
jest niejawnie typ czasu kompilacji wyrażenia, jeśli ma taki typ. W przeciwnym razie sam interfejs IDisposable
jest używany jako ResourceType
. Zmienna resource
jest niedostępna i niewidoczna dla osadzonej instrukcji.
Gdy resource_acquisition ma formę local_variable_declaration, można uzyskać wiele zasobów danego typu. Instrukcja using
formularza
using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) «statement»
jest dokładnie odpowiednikiem sekwencji zagnieżdżonych using
instrukcji:
using (ResourceType r1 = e1)
using (ResourceType r2 = e2)
...
using (ResourceType rN = eN)
«statement»
Przykład: Poniższy przykład tworzy plik o nazwie log.txt i zapisuje dwa wiersze tekstu w pliku. Następnie przykład otwiera ten sam plik do odczytu i kopiuje zawarte wiersze tekstu do konsoli.
class Test { static void Main() { using (TextWriter w = File.CreateText("log.txt")) { w.WriteLine("This is line one"); w.WriteLine("This is line two"); } using (TextReader r = File.OpenText("log.txt")) { string s; while ((s = r.ReadLine()) != null) { Console.WriteLine(s); } } } }
TextWriter
Ponieważ klasy iTextReader
implementująIDisposable
interfejs, przykład może użyćusing
instrukcji, aby upewnić się, że plik źródłowy jest poprawnie zamknięty po operacjach zapisu lub odczytu.przykład końcowy
13.15 Instrukcja wydajności
Instrukcja yield
jest używana w bloku iteratora (§13.3), aby uzyskać wartość obiektu wyliczającego (§15.14.5) lub obiekt wyliczalny (§15.14.6) iteratora lub sygnalizować koniec iteracji.
yield_statement
: 'yield' 'return' expression ';'
| 'yield' 'break' ';'
;
yield
jest kontekstowym słowem kluczowym (§6.4.4) i ma specjalne znaczenie tylko wtedy, gdy jest używane bezpośrednio przed return
słowem kluczowym lub break
.
Istnieje kilka ograniczeń dotyczących tego, gdzie można wyświetlić instrukcję yield
, zgodnie z opisem w poniższym artykule.
- Jest to błąd czasu kompilacji instrukcji
yield
(formularza) wyświetlanej poza method_body, operator_body lub accessor_body. - Jest to błąd czasu kompilacji instrukcji
yield
(z dowolnego formularza) wyświetlanej wewnątrz funkcji anonimowej. - Jest to błąd czasu kompilacji instrukcji
yield
(z dowolnego formularza), która ma być wyświetlana wfinally
klauzuli instrukcjitry
. - Jest to błąd czasu kompilacji instrukcji
yield return
, która ma być wyświetlana w dowolnym miejscu wtry
instrukcji zawierającej dowolne catch_clauses.
Przykład: W poniższym przykładzie pokazano prawidłowe i nieprawidłowe zastosowania instrukcji
yield
.delegate IEnumerable<int> D(); IEnumerator<int> GetEnumerator() { try { yield return 1; // Ok yield break; // Ok } finally { yield return 2; // Error, yield in finally yield break; // Error, yield in finally } try { yield return 3; // Error, yield return in try/catch yield break; // Ok } catch { yield return 4; // Error, yield return in try/catch yield break; // Ok } D d = delegate { yield return 5; // Error, yield in an anonymous function }; } int MyMethod() { yield return 1; // Error, wrong return type for an iterator block }
przykład końcowy
Niejawna konwersja (§10.2) istnieje z typu wyrażenia w yield return
instrukcji do typu rentowności (§15.14.4) iteratora.
Instrukcja yield return
jest wykonywana w następujący sposób:
- Wyrażenie podane w instrukcji jest obliczane, niejawnie konwertowane na typ plonu i przypisywane do
Current
właściwości obiektu wyliczającego. - Wykonanie bloku iteratora jest zawieszone.
yield return
Jeśli instrukcja znajduje się w co najmniej jednymtry
bloku, skojarzonefinally
bloki nie są obecnie wykonywane. MoveNext
Metoda obiektu modułu wyliczającego powracatrue
do obiektu wywołującego, co wskazuje, że obiekt modułu wyliczającego pomyślnie został zaawansowany do następnego elementu.
Następne wywołanie metody obiektu MoveNext
modułu wyliczającego wznawia wykonywanie bloku iteratora z miejsca ostatniego wstrzymania.
Instrukcja yield break
jest wykonywana w następujący sposób:
yield break
Jeśli instrukcja jest ujęta przez co najmniej jedentry
blok ze skojarzonymifinally
blokami, kontrolka jest początkowo przenoszona dofinally
bloku najbardziej wewnętrznejtry
instrukcji. Gdy kontrolka i jeśli osiągnie punktfinally
końcowy bloku, kontrolka zostanie przeniesiona dofinally
bloku następnej instrukcji otaczającejtry
. Ten proces jest powtarzany do momentufinally
wykonania bloków wszystkich ujęćtry
instrukcji.- Kontrolka jest zwracana do obiektu wywołującego blok iteratora. Jest
MoveNext
to metoda lubDispose
metoda obiektu modułu wyliczającego.
yield break
Ponieważ oświadczenie bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji yield break
nigdy nie jest osiągalny.
ECMA C# draft specification