Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
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 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 umieszczenie deklaracjii
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łanieConsole.WriteLine
jest uznawane za nieosiągalne. Jeślii
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ż 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:
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 instrukcjibreak
.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 instrukcjeyield 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ą ref
variable_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
igoto
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ątkowyL
nie jest osiągalny. Ponieważ początekL
nie jest dostępny, instrukcja po końcuL
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łaniathis
, 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ż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 zdania.
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 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ń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śli nie ma częścielse
, sterowanie przekazywane jest do punktu końcowego instrukcjiif
.
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 instrukcjiswitch
. - 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 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
default
jest obecna, kontrola zostanie przekazana 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 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
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 czyni punkt końcowy listy instrukcji niedostępnym, jest dozwolona. Na przykład, instrukcjawhile
kontrolowana przez wyrażenie logicznetrue
jest znane, że nigdy nie osiąga punktu końcowego. Podobnie, instrukcjathrow
lubreturn
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
switch
string
. 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 etykietycase
. notatka końcowa Gdy typ zarządzający instrukcjiswitch
jeststring
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.
- etykieta jest
- 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
- Etykieta przełącznika jest referencją dla osiągalnej instrukcji
goto case
lubgoto 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 instrukcjicontinue
, zostaje ono przeniesione na początek instrukcjiwhile
. - Jeśli wyrażenie logiczne daje wartość
false
, sterowanie jest przenoszone do końca instrukcjiwhile
.
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ś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:
- 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 instrukcjido
. W przeciwnym razie kontrolka jest przenoszona do punktu końcowego instrukcjido
.
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 instrukcjicontinue
), 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ńcowegofor
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ś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
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 interfejsIEnumerable
(ponieważSystem.Array
implementuje ten interfejs). Typ kolekcji to interfejsIEnumerable
, typem modułu wyliczającego to interfejsIEnumerator
, a typ iteracji to typ elementu tablicyX
. - Jeśli typ
X
wyrażenia jestdynamic
, istnieje niejawna konwersja z wyrażenia do interfejsuIEnumerable
(§10.2.10). Typ kolekcji jest interfejsemIEnumerable
, a typ wyliczającego jest interfejsemIEnumerator
.var
Jeśli identyfikator jest podany jako local_variable_type, to typ iteracji jestdynamic
, w przeciwnym razie jestobject
. - W przeciwnym razie określ, czy typ
X
ma odpowiednią metodęGetEnumerator
:- Przeprowadź wyszukiwanie członków dla typu
X
z identyfikatoremGetEnumerator
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
metodyGetEnumerator
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 identyfikatoremCurrent
. 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 identyfikatoremMoveNext
. 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 jestE
, a typ iteracji jest typem właściwościCurrent
. 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.
- Przeprowadź wyszukiwanie członków dla typu
- W przeciwnym razie sprawdź interfejs enumerowalny:
- Jeśli wśród wszystkich typów
Tᵢ
, dla których istnieje niejawna konwersja zX
doIEnumerable<Tᵢ>
, istnieje unikatowy typT
, taki żeT
nie jestdynamic
i dla wszystkich pozostałychTᵢ
istnieje niejawna konwersja zIEnumerable<T>
doIEnumerable<Tᵢ>
, to typ kolekcji jest interfejsemIEnumerable<T>
, typ wyliczający jest interfejsemIEnumerator<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ą podjęte żadne dalsze kroki. - W przeciwnym razie, jeśli istnieje niejawna konwersja z
X
do interfejsuSystem.Collections.IEnumerable
, wtedy typ kolekcji jest tym interfejsem, typ enumeratora to interfejsSystem.Collections.IEnumerator
, a typ iteracji toobject
. - 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 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 foreach
foreach (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ętlifor
będzie wartością końcową,13
, którą wywołanief
wydrukuje. Zamiast tego, ponieważ każda iteracja ma swoją własną zmiennąv
, ta przechwycona przezf
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łyv
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 interfejsuSystem.IDisposable
, wówczasJeśli
E
jest typem wartości niemającym wartości null, klauzulafinally
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, konwersjae
naSystem.IDisposable
nie powoduje pakowania.
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 iteracjinumbers
.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 identyfikatoremGetAsyncEnumerator
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
metodyGetAsyncEnumerator
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 identyfikatoremCurrent
. 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 identyfikatoremMoveNextAsync
. 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 jestE
, a typ iteracji jest typem właściwościCurrent
.
- Przeprowadź wyszukiwanie członków dla typu
- W przeciwnym razie sprawdź interfejs wyliczalny asynchroniczny:
- Jeśli wśród wszystkich typów
Tᵢ
, dla których istnieje niejawna konwersja zX
doIAsyncEnumerable<Tᵢ>
, istnieje unikatowy typT
, taki żeT
nie jestdynamic
i dla wszystkich pozostałychTᵢ
istnieje niejawna konwersja zIAsyncEnumerable<T>
doIAsyncEnumerable<Tᵢ>
, to typ kolekcji jest interfejsemIAsyncEnumerable<T>
, typ wyliczający jest interfejsemIAsyncEnumerator<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ą podjęte żadne dalsze kroki. - 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 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 await
ać 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 dwiematry
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ówtry
ze skojarzonymi blokamifinally
, kontrola jest początkowo przekazywana do blokufinally
najbardziej wewnętrznej instrukcjitry
. Gdy przepływ sterowania osiągnie punkt końcowy blokufinally
, zostanie przeniesiony do blokufinally
następnej otaczającej instrukcjitry
. Ten proces jest powtarzany, aż bloki wszystkich interweniującychfinally
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ówtry
ze skojarzonymi blokamifinally
, kontrola jest początkowo przekazywana do blokufinally
najbardziej wewnętrznej instrukcjitry
. Gdy przepływ sterowania osiągnie punkt końcowy blokufinally
, zostanie przeniesiony do blokufinally
następnej otaczającej instrukcjitry
. Ten proces jest powtarzany, aż bloki wszystkich interweniującychfinally
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 goto
identyfikatora 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ł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 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ówtry
ze skojarzonymi blokamifinally
, kontrola jest początkowo przekazywana do blokufinally
najbardziej wewnętrznej instrukcjitry
. Gdy przepływ sterowania osiągnie punkt końcowy blokufinally
, zostanie przeniesiony do blokufinally
następnej otaczającej instrukcjitry
. Ten proces jest powtarzany, aż bloki wszystkich interweniującychfinally
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.
return
instrukcje 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 lubtry
więcejcatch
bloków ze skojarzonymifinally
blokami, kontrolka jest początkowo przenoszona dofinally
bloku najbardziej wewnętrznejtry
instrukcji. Gdy przepływ sterowania osiągnie punkt końcowy blokufinally
, zostanie przeniesiony do blokufinally
następnej otaczającej instrukcjitry
. Ten proces jest powtarzany do momentu, aż bloki wszystkich obejmujących instrukcjifinally
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ętrznejS
instrukcjitry
i kończącej się najbardziej zewnętrznątry
instrukcją, oceniane są następujące kroki:- Jeśli blok
try
obejmuje punkt rzutu i jeśliS
ma co najmniej jedną klauzulęS
, klauzulecatch
są badane w kolejności występowania, aby znaleźć odpowiednią procedurę obsługi dla wyjątku. Pierwszacatch
klauzula, która określa typ wyjątkuT
(lub parametr typu, który w czasie wykonywania oznacza typ wyjątkuT
) w taki sposób, że typ czasu wykonywaniaE
pochodzi zT
, 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 klauzulacatch
zawiera filtr wyjątku, ta klauzula jest traktowana jako zgodna, jeśli filtr wyjątkucatch
oceni się natrue
prawda. Klauzula ogólnacatch
(§13.11) jest uważana za zgodną z dowolnym typem wyjątku. Jeśli zostanie znaleziona odpowiednia klauzulacatch
, propagacja wyjątku jest zakończona przez przeniesienie kontroli do bloku tej klauzulicatch
. - W przeciwnym razie, jeśli blok
try
lub blokcatch
S
otacza punkt rzutu i jeśliS
ma blokfinally
, kontrola jest przekazywana do blokufinally
. Jeśli blokfinally
zgłasza inny wyjątek, przetwarzanie bieżącego wyjątku zostanie zakończone. W przeciwnym razie, gdy kontrola osiągnie punkt końcowy blokufinally
, przetwarzanie bieżącego wyjątku będzie kontynuowane.
- Jeśli blok
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.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ó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 break
instrukcji , 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ącychcatch
klauzul, stosując wszelkie filtry wyjątków. Następnie klauzulafinally
wMethod
jest wykonywana przed przeniesieniem kontroli do otaczającej pasującej klauzulicatch
. 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 jednegocatch
bloku jest osiągalny. - Jeśli blok
finally
jest obecny, punkt końcowy blokufinally
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 (IAsyncDisposable
dla 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 resource
System.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
iTextReader
implementują interfejsIDisposable
, przykład może użyć instrukcjiusing
, 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 klauzulifinally
instrukcjitry
jest błędem czasu kompilacji. - Jest to błąd czasu kompilacji, gdy instrukcja
yield return
pojawia się w dowolnym miejscu w instrukcjitry
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 jednymtry
bloku, skojarzonefinally
bloki nie są wykonywane w tym momencie. -
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 objęta przez co najmniej jedentry
blok ze skojarzonymifinally
blokami, sterowanie jest początkowo przenoszone dofinally
bloku najgłębszejtry
instrukcji. Gdy przepływ sterowania osiągnie punkt końcowy blokufinally
, zostanie przeniesiony do blokufinally
następnej otaczającej instrukcjitry
. Ten proces jest powtarzany do momentu, aż bloki wszystkich obejmujących instrukcjifinally
zostały wykonane. - Kontrolka jest zwracana do obiektu wywołującego blok iteratora. Jest to metoda
MoveNext
lub metodaDispose
obiektu wyliczającego.
Ponieważ instrukcja yield break
bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji yield break
nigdy nie jest osiągalny.
ECMA C# draft specification