Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz 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 (§24.2) i fixed_statement (§24.7) są dostępne tylko w niebezpiecznym kodzie (§24).
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ż
ifinstrukcja wymaga embedded_statement, zamiast instrukcji dla jejifgałęzi. Gdyby ten kod był dozwolony, zmiennaizostanie zadeklarowana, ale nigdy nie można jej użyć. Należy jednak pamiętać, że umieszczenie deklaracjiiw 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.25), 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
ifjest 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.WriteLinejest uznawane za nieosiągalne. Jeśliijednak zostanie zmieniona na zmienną lokalnąvoid F() { int i = 1; if (i == 2) Console.WriteLine("reachable"); }To
Console.WriteLinewywoł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.WriteLinejest określana w następujący sposób:
- Pierwsza
Console.WriteLineinstrukcja wyrażenia jest osiągalna, ponieważ blokFmetody jest osiągalny (§13.3).- Punkt końcowy pierwszej
Console.WriteLineinstrukcji wyrażenia jest osiągalny, ponieważ ta instrukcja jest osiągalna (§13.7 i §13.3).- Instrukcja
ifjest osiągalna, ponieważ punkt końcowy pierwszejConsole.WriteLineinstrukcji wyrażenia jest osiągalny (§13.7 i §13.3).- Druga
Console.WriteLineinstrukcja wyrażenia jest osiągalna, ponieważ wyrażenie logiczne instrukcjiifnie 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
switchnie 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
returnpojawia się w bloku iteratora (ale instrukcjeyield returnsą dozwolone). - Jest to błąd czasu kompilacji dla bloku iteratora zawierającego niebezpieczny kontekst (§24.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ą
gotoinstrukcję.
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
whileinstrukcji 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
gotoinstrukcje 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
varznajduje 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 local_variable_declaration , które nie występują jako for_initializer (§13.9.4) lub resource_acquisition (§13.14), ta lewa do prawej kolejności jest równoważna każdemu deklaratorowi znajdującemu się w oddzielnej local_variable_declaration. Przykład:
void F() { int x = 1, y, z = x * 2; }jest odpowiednikiem:
void F() { int x = 1; int y; int z = x * 2; }notatka końcowa
Wartość zmiennej lokalnej jest uzyskiwana w wyrażeniu przy użyciu simple_name (§12.8.4). Zmienna lokalna musi być zdecydowanie przypisana (§9.4) w każdej lokalizacji, w której uzyskuje się jej wartość. Każda zmienna lokalna wprowadzona przez local_variable_declarationjest początkowo nieprzypisane (§9.4.3). Jeśli deklarator ma wyrażenie inicjacyjne, wprowadzona zmienna lokalna jest klasyfikowana jako przypisana na końcu deklaratora (§9.4.4.5).
Zakres zmiennej lokalnej wprowadzonej przez local_variable_declaration jest definiowany w następujący sposób (§7.7):
- Jeśli deklaracja występuje jako for_initializer , zakresem jest for_initializer, for_condition, for_iterator i embedded_statement (§13.9.4);
- Jeśli deklaracja występuje jako resource_acquisition zakres jest najbardziej zewnętrznym blokiem semantycznie równoważnego rozszerzenia using_statement (§13.14);
- W przeciwnym razie zakres to blok, w którym występuje deklaracja.
Jest to błąd odwoływania się do zmiennej lokalnej według nazwy w pozycji tekstowej, która poprzedza deklaratora lub w dowolnym wyrażeniu inicjującym w deklaratorze. W zakresie zmiennej lokalnej występuje błąd czasu kompilacji, gdy zadeklaruje się inną zmienną lokalną, funkcję lokalną lub stałą o tej samej nazwie.
Kontekst ref-safe zmiennej lokalnej ref (§9.7.2) jest taki sam jak kontekst ref-safe odniesienia variable_reference podczas inicjalizacji. Kontekst ref-safe-context zmiennych lokalnych innych niż ref jest blok deklaracji.
13.6.2.2 Niejawnie wpisane deklaracje zmiennych lokalnych
implicitly_typed_local_variable_declaration
: 'var' implicitly_typed_local_variable_declarator
| ref_kind 'var' ref_local_variable_declarator
;
implicitly_typed_local_variable_declarator
: identifier '=' expression
;
Deklaracja zmiennej lokalnej o typie niejawnie określonym wprowadza pojedynczą zmienną lokalną, identyfikator.
Wyrażenie lub variable_reference mają typ czasu kompilacji. T Pierwsza alternatywa deklaruje zmienną z początkową wartością wyrażenia; jego typ jest T? , gdy T jest typem referencyjnym bez wartości null, w przeciwnym razie jego typem jest T. Druga alternatywa deklaruje zmienną ref z początkową wartością refvariable_reference; jego typem jest ref T? gdy T jest typem referencyjnym niemającym wartości null, w przeciwnym razie jego typem jest ref T. (ref_kind jest opisany w §15.6.1.)
Przykład:
var i = 5; var s = "Hello"; var d = 1.0; var numbers = new int[] {1, 2, 3}; var orders = new Dictionary<int,Order>(); ref var j = ref i; ref readonly var k = ref i;Niejawnie typizowane deklaracje zmiennych lokalnych są dokładnie równoważne następującym jawnie wpisanym deklaracjom:
int i = 5; string s = "Hello"; double d = 1.0; int[] numbers = new int[] {1, 2, 3}; Dictionary<int,Order> orders = new Dictionary<int,Order>(); ref int j = ref i; ref readonly int k = ref i;Poniżej przedstawiono niepoprawne niejawnie wpisane deklaracje zmiennych lokalnych:
var x; // Error, no initializer to infer type from var y = {1, 2, 3}; // Error, array initializer not permitted var z = null; // Error, null does not have a type var u = x => x + 1; // Error, anonymous functions do not have a type var v = v++; // Error, initializer cannot refer to v itselfprzykł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
;
Explicitly_typed_local_variable_declaration wprowadza co najmniej jedną zmienną lokalną o określonym typie.
Jeśli local_variable_initializer jest obecny, jego typ jest odpowiedni zgodnie z regułami prostego przypisania (§12.23.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.23.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.25), 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 (§24.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 (§24.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 dotyczące
thisfunkcji anonimowych igotodublowania ich w §12.21.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ść
Ljest osiągalna, mimo że punkt początkowyLnie jest osiągalny. Ponieważ początekLnie jest dostępny, instrukcja po końcuLnie 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,baseani 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 + yix == 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
ifif (x) if (y) F(); else G();jest równoważny
if (x) { if (y) { F(); } else { G(); } }przykład końcowy
Instrukcja if jest wykonywana w następujący sposób:
- Ocenia się boolean_expression (§12.26).
- 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ńcowegoifinstrukcji. - Jeśli wyrażenie logiczne zwraca wartość
falsei jeślielseczęść 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ńcowegoifinstrukcji. - Jeśli wyrażenie logiczne zwraca wartość
falsei 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 selector_expression przełącznika.
switch_statement
: 'switch' selector_expression switch_block
;
selector_expression
: '(' expression ')'
| tuple_expression
;
switch_block
: '{' switch_section* '}'
;
switch_section
: switch_label+ statement_list
;
switch_label
: 'case' pattern case_guard? ':'
| 'default' ':'
;
case_guard
: 'when' null_coalescing_expression
;
Switch_statement składa się ze słowa kluczowego switch, po którym następuje wyrażenie tuple_expression lub nawias (każde z nich jest nazywane selector_expression), 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żdy switch_label zawierający case ma skojarzony wzorzec (§11), względem którego jest testowana wartość selector_expression 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.
Uwaga: dla wygody nawiasy w switch_statement można pominąć, gdy selector_expression jest tuple_expression. Można na przykład
switch ((a, b)) …zapisać jakoswitch (a, b) …. notatka końcowa
Typ zarządzający instrukcji switch jest ustanawiany przez selector_expression przełącznika.
- Jeśli typ selector_expression przełącznika to
sbyte, ,shortushortuintulonglongintboolbytecharlubstringenum_type, lub jeśli jest to typ wartości dopuszczający wartość null odpowiadający jednemu z tych typów, jest to typswitchzarządzający instrukcji . - W przeciwnym razie, jeśli dokładnie jedna niejawna konwersja zdefiniowana przez użytkownika istnieje z typu selector_expression przełącznika do jednego z następujących możliwych typów zarządzania:
sbyte,ushortlongstringshortbyteuintulongintcharlub, typu wartości dopuszczanej do wartości null odpowiadającego jednemu z tych typów, przekonwertowany typ jest typemswitchzarządzającym instrukcji. - W przeciwnym razie typ zarządzający instrukcji
switchjest typem selector_expression 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:
- Selector_expression przełącznika jest obliczany i konwertowany na typ zarządzający.
- Kontrolka jest przekazywana zgodnie z wartością selector_expression przekonwertowanego przełącznika:
- Wzorzec leksykalnie pierwszy w zestawie
caseetykiet w tej samejswitchinstrukcji, która pasuje do wartości selector_expression przełącznika, i dla którego wyrażenie guard jest nieobecne lub daje w wyniku wartość true, powoduje przeniesienie kontrolki na listę instrukcji po dopasowanejcaseetykiecie. - W przeciwnym razie, jeśli etykieta
defaultjest obecna, kontrola zostanie przekazana na listę instrukcji po etykieciedefault. - W przeciwnym razie kontrolka jest przenoszona do punktu końcowego instrukcji
switch.
- Wzorzec leksykalnie pierwszy 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 caselubgoto 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
breakinstrukcje zostaną przypadkowo pominięte. Na przykład sekcje powyższejswitchinstrukcji 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 caselubgoto default, ale każda konstrukcja, która czyni punkt końcowy listy instrukcji niedostępnym, jest dozwolona. Na przykład, instrukcjawhilekontrolowana przez wyrażenie logicznetruejest znane, że nigdy nie osiąga punktu końcowego. Podobnie, instrukcjathrowlubreturnzawsze przekazuje sterowanie gdzie indziej i nigdy nie osiąga punktu końcowego. W związku z tym następujący przykład jest prawidłowy:switch (i) { case 0: while (true) { F(); } case 1: throw new ArgumentException(); case 2: return; }notatka końcowa
Przykład: Typ zarządzający instrukcji może być typem
switchstring. Przykład:void DoCommand(string command) { switch (command.ToLower()) { case "run": DoRun(); break; case "save": DoSave(); break; case "quit": DoQuit(); break; default: InvalidCommand(command); break; } }przykład końcowy
Uwaga: Podobnie jak operatory równości ciągów (§12.14.8), instrukcja uwzględnia wielkość liter i wykonuje daną sekcję przełącznika tylko wtedy,
switchgdy ciąg selector_expression przełącznika dokładnie pasuje do stałejcaseetykiety. notatka końcowa
Gdy typ zarządzający instrukcji switch jest string lub typ wartości dopuszczanej do wartości null, wartość null jest dozwolona case jako stała etykiety.
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:
-
Selector_expression przełącznika jest wartością stałą 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
-
Selector_expression przełącznika nie jest wartością stałą i albo
- etykieta
casejest bez ochrony lub z strażnikiem, którego wartość nie jest stałą wartością false; lub - jest to etykieta
defaulti- 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 caselubgoto 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
switchzawiera osiągalnąbreakinstrukcję, która kończy instrukcjęswitch. - Nie ma etykiety
default, a także- Selector_expression przełącznika jest niestałą wartością, a zestaw wzorców pojawiających się wśród przypadków instrukcji przełącznika, które nie mają strażników lub mają osłony, których wartość jest stałą true, nie jest wyczerpująca (§11.4) dla typu zarządzającego przełącznikiem.
-
Selector_expression przełącznika jest niestałych wartości typu dopuszczającego wartość null, a żaden wzorzec nie pojawia się wśród przypadków instrukcji switch, które nie mają strażników lub mają strażników, których wartość jest stałą true, będzie zgodna z wartością
null. -
Selector_expression przełącznika jest stałą wartością i żadna
caseetykieta bez ochrony lub której ochrona jest stałą true, będzie zgodna z tej wartości.
Przykład: Poniższy kod przedstawia zwięzłe użycie klauzuli
when:static object CreateShape(string shapeDescription) { switch (shapeDescription) { case "circle": return new Circle(2); … case var o when string.IsNullOrWhiteSpace(o): return null; default: return "invalid shape description"; } }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:
- Ocenia się boolean_expression (§12.26).
- 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
whilezawiera osiągalnąbreakinstrukcję, która kończy instrukcjęwhile. - Instrukcja
whilejest 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.
- Kiedy i jeśli kontrolka osiągnie punkt końcowy osadzonej instrukcji (prawdopodobnie z wykonania
continueinstrukcji), boolean_expression (§12.26) jest obliczany. 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
dozawiera osiągalnąbreakinstrukcję, 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.26).
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ńcowegoforinstrukcji.
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
forjest osiągalna, a for_condition nie występuje. - Instrukcja
forjest 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
forzawiera osiągalnąbreakinstrukcję, która kończy instrukcjęfor. - Instrukcja
forjest 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 foreach w czasie kompilacji instrukcji najpierw określa typ kolekcji (C), typ wyliczania (E) i typ iteracji (Tref Tlub ref readonly T) wyrażenia.
Determinacja jest podobna dla wersji synchronicznych i asynchronicznych. Różne interfejsy z różnymi metodami i typami zwracanymi rozróżniają wersje synchroniczne i asynchroniczne. Proces ogólny jest kontynuowany w następujący sposób. Nazwy w elementach "«" i "»" są symbolami zastępczymi dla rzeczywistych nazw iteratorów synchronicznych i asynchronicznych. Typy dozwolone dla «GetEnumerator», «MoveNext», «IEnumerable»<T>, «IEnumerator»<T> i wszelkie inne różnice są szczegółowo opisane w §13.9.5.2 dla instrukcji synchronicznej, a w foreach dla instrukcji asynchronicznej foreach.
- Ustal, czy typ
Xwyrażenia ma odpowiednią metodę "GetEnumerator":- Przeprowadź wyszukiwanie składowych na typie
Xz identyfikatorem «GetEnumerator» i bez argumentów typu. Jeśli odnośnik elementu członkowskiego nie generuje dopasowania lub generuje niejednoznaczność lub tworzy dopasowanie, które nie jest grupą metod, sprawdź interfejs wyliczalny zgodnie z opisem w kroku 2. 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
Emetody "GetEnumerator" nie jest klasą, strukturą ani typem interfejsu, utwórz błąd i nie podejmij dalszych kroków. - Przeprowadź wyszukiwanie
Eskładowych przy użyciu identyfikatoraCurrenti bez argumentów typu. Jeśli wyszukiwanie elementu członkowskiego nie daje dopasowania, wynik jest błędem lub wynikiem jest wszystko, z wyjątkiem właściwości wystąpienia publicznego, która zezwala na odczytywanie, generuje błąd i nie podejmuje dalszych kroków. - Przeprowadź wyszukiwanie
Eskładowych przy użyciu identyfikatora "MoveNext" i bez argumentów typu. Jeśli wyszukiwanie składowe nie daje dopasowania, wynik jest błędem lub wynikiem jest coś poza grupą metod, generuje błąd i nie podejmij żadnych dalszych kroków. - Przeprowadź rozpoznawanie przeciążenia w grupie metod z pustą listą argumentów. Jeśli rozwiązanie przeciążenia powoduje: nie ma odpowiednich metod; niejednoznaczność; lub pojedyncza najlepsza metoda, ale ta metoda jest statyczna, a nie publiczna lub jej typ zwracany nie jest dozwolonym typem zwracanym; następnie wygeneruj błąd i nie podejmij dalszych kroków.
- Typ kolekcji to
X, typ modułu wyliczającego jestE, a typ iteracji jest typem właściwościCurrent.
- Przeprowadź wyszukiwanie składowych na typie
- W przeciwnym razie sprawdź interfejs enumerowalny:
- Jeśli wśród wszystkich typów
Tᵢ, dla których istnieje niejawna konwersja zXna «IEnumerable»<Ti>, istnieje unikatowy typT, któryTniedynamicjest i dla wszystkich innychTᵢistnieje niejawna konwersja z «IEnumerable»<T> do «IEnumerable»<Ti>, typ kolekcji jest interfejsem «IEnumerable»T, typ wyliczający jest interfejsem «IEnumerator»<>T<>, a typ iteracji toT. - W przeciwnym razie, jeśli istnieje więcej niż jeden taki typ
T, wygeneruj błąd i nie podejmij dalszych kroków.
- Jeśli wśród wszystkich typów
Uwaga: jeśli wyrażenie ma wartość
null,System.NullReferenceExceptionelement 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 rozszerzeniami opisanymi w §13.9.5.2 i §13.9.5.3.
13.9.5.2 Synchroniczne foreach
Synchroniczny foreach nie zawiera słowa kluczowego awaitforeach przed słowem kluczowym. Określenie typu kolekcji, typu wyliczenia i typu iteracji jest kontynuowane zgodnie z opisem w §13.9.5.1, gdzie:
- «GetEnumerator» to
GetEnumeratormetoda. - «MoveNext» to
MoveNextmetoda z typem zwracanymbool. - «IEnumerable»<T> to
System.Collections.Generic.IEnumerable<T>interfejs. - «IEnumerator»<T> jest interfejsem
System.Collections.Generic.IEnumerator<T>.
Ponadto następujące modyfikacje są wprowadzane do kroków w §13.9.5.1:
Przed procesem opisanym w §13.9.5.1 należy wykonać następujące czynności:
- Jeśli typ
Xjest typem tablicy, istnieje niejawna konwersja odwołania zXdo interfejsuIEnumerable<T>, gdzieTjest typem elementu tablicyX(§17.2.3). - Jeśli typ
Xwyrażenia jestdynamic, istnieje niejawna konwersja z wyrażenia do interfejsuIEnumerable(§10.2.10). Typ kolekcji jest interfejsemIEnumerable, a typ wyliczającego jest interfejsemIEnumerator.varJeśli identyfikator jest podany jako local_variable_type, to typ iteracji jestdynamic, w przeciwnym razie jestobject.
Jeśli proces w §13.9.5.1 zakończy się bez tworzenia pojedynczego typu kolekcji, typu wyliczającego i typu iteracji, należy wykonać następujące czynności:
- Jeśli istnieje niejawna konwersja z
Xdo interfejsuSystem.Collections.IEnumerable, typ kolekcji jest tym interfejsem, typem modułu wyliczającego jest interfejsSystem.Collections.IEnumerator, a typem iteracji jestobject. - W przeciwnym razie zostanie wygenerowany błąd i nie zostaną podjęte żadne dalsze kroki.
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 v referencyjna jest odczyt-zapis w instrukcji osadzonej, ale v nie zostanie ponownie przypisana (§12.23.3). Jeśli nie istnieje konwersja tożsamości (§10.2.2) z T (typ iteracji) na V ( local_variable_type w foreach instrukcji), zostanie wygenerowany błąd i nie zostaną wykonane żadne dalsze kroki.
Instrukcja w formie foreachforeach (ref readonly V v in x) «embedded_statement» ma podobną równoważną formę, ale zmienna referencyjna v znajduje się w instrukcji osadzonej ref readonly, i w związku z tym nie może być przypisana ze słowem kluczowym ref ani przypisana ponownie.
Umieszczenie v wewnątrz while pętli jest ważne dla sposobu przechwytywania (§12.21.6.2) przez dowolną funkcję anonimową wykonywaną 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
vw rozszerzonej formie został zadeklarowany poza pętląwhile, będzie on współdzielony we wszystkich iteracjach, a jego wartość po pętliforbędzie wartością końcową,13, którą wywołaniefwydrukuje. Zamiast tego, ponieważ każda iteracja ma swoją własną zmiennąv, ta przechwycona przezfw pierwszej iteracji będzie nadal przechowywać wartość7, która zostanie wydrukowana. (Należy pamiętać, że wcześniejsze wersje języka C# deklarowałyvpoza pętląwhile.)przykład końcowy
Treść bloku finally jest skonstruowana zgodnie z następującymi krokami:
Jeśli istnieje niejawna konwersja z
Edo interfejsuSystem.IDisposable, wówczasJeśli
Ejest typem wartości niemającym wartości null, klauzulafinallyrozszerza się do semantycznego odpowiednika:finally { ((System.IDisposable)e).Dispose(); }W przeciwnym razie klauzula
finallyjest rozszerzana na semantyczny odpowiednik:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }z wyjątkiem tego, że jeśli
Ejest typem wartościowym lub parametrem typu utworzonego jako typ wartościowy, konwersjaenaSystem.IDisposablenie powoduje pakowania.
W przeciwnym razie, jeśli
Ejest typem zapieczętowanym, klauzulafinallyzostanie rozszerzona do pustego bloku:finally {}W przeciwnym razie klauzula
finallyjest 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.9przykł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
njest wnioskowany jakoint, typ iteracjinumbers.przykład końcowy
13.9.5.3 await foreach
Asynchroniczny foreach używa await foreach składni . Określenie typu kolekcji, typu wyliczenia i typu iteracji jest kontynuowane zgodnie z opisem w §13.9.5.1, gdzie:
- «GetEnumerator» to metoda, która
GetEnumeratorAsyncma oczekiwany typ zwracany (§12.9.9.2). - «MoveNext» to
MoveNextAsyncmetoda, która ma oczekiwany typ zwracany (§12.9.9.2), gdzie await_expression jest klasyfikowana jakobool(§12.9.9.3). - «IEnumerable»<T> to
System.Collections.Generic.IAsyncEnumerable<T>interfejs. - «IEnumerator»<T> jest interfejsem
System.Collections.Generic.IAsyncEnumerator<T>.
Jest to błąd typu iteracjiawait foreach instrukcji jako zmiennej referencyjnej (§9.7).
Deklaracja await foreach formularza
await foreach (T item in enumerable) «embedded_statement»
jest semantycznie równoważne:
var enumerator = enumerable.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
T item = enumerator.Current;
«embedded_statement»
}
}
finally
{
// dispose of enumerator as described later in this clause.
}
W przypadku, gdy wyrażenie enumerable reprezentuje wyrażenie wywołania metody, a jeden z parametrów jest oznaczony znakiem EnumeratorCancellationAttribute (§23.5.8) CancellationToken jest przekazywany do GetAsyncEnumerator metody. Inne metody biblioteki mogą wymagać przekazania CancellationTokendo GetAsyncEnumerator klasy . Gdy te metody są częścią wyrażenia enumerable, tokeny są łączone w jeden token tak, jakby CreateLinkedTokenSource i jego Token właściwość.
Treść bloku finally jest skonstruowana zgodnie z następującymi krokami:
Jeśli
Ema dostępnąDisposeAsync()metodę, w której typ zwracany jest oczekiwany (§12.9.9.2), klauzulafinallyjest rozszerzana na semantyczny odpowiednik:finally { await e.DisposeAsync(); }W przeciwnym razie, jeśli istnieje niejawna konwersja z
Edo interfejsuSystem.IAsyncDisposableiEjest typem wartości niepustej, klauzulafinallyzostanie rozszerzona do semantycznego odpowiednika:finally { await ((System.IAsyncDisposable)e).DisposeAsync(); }z wyjątkiem tego, że jeśli
Ejest typem wartościowym lub parametrem typu utworzonego jako typ wartościowy, konwersjaenaSystem.IAsyncDisposablenie powoduje pakowania.W przeciwnym razie, jeśli
Ejest typemref structi ma dostępnąDispose()metodę, klauzulafinallyjest rozszerzana na semantyczny odpowiednik:finally { e.Dispose(); }W przeciwnym razie, jeśli
Ejest typem zapieczętowanym, klauzulafinallyzostanie rozszerzona do pustego bloku:finally {}W przeciwnym razie klauzula
finallyjest rozszerzana na:finally { System.IAsyncDisposable d = e as System.IAsyncDisposable; if (d != null) { await d.DisposeAsync(); } }
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.
Uwaga: Nie
await foreachjest wymagane usunięcieesynchronicznego mechanizmu usuwania, jeśli mechanizm usuwania asynchronicznego nie jest dostępny. notatka końcowa
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"); } }
finallybloki skojarzone z dwiematryinstrukcjami 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 breakprzykł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
breakopuszcza jeden lub więcej blokówtryze skojarzonymi blokamifinally, kontrola jest początkowo przekazywana do blokufinallynajbardziej wewnętrznej instrukcjitry. Gdy przepływ sterowania osiągnie punkt końcowy blokufinally, zostanie przeniesiony do blokufinallynastępnej otaczającej instrukcjitry. Ten proces jest powtarzany, aż bloki wszystkich interweniującychfinallyinstrukcji 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
continueopuszcza jeden lub więcej blokówtryze skojarzonymi blokamifinally, kontrola jest początkowo przekazywana do blokufinallynajbardziej wewnętrznej instrukcjitry. Gdy przepływ sterowania osiągnie punkt końcowy blokufinally, zostanie przeniesiony do blokufinallynastępnej otaczającej instrukcjitry. Ten proces jest powtarzany, aż bloki wszystkich interweniującychfinallyinstrukcji zostaną wykonane. - Kontrolka jest przenoszona do elementu docelowego instrukcji
continue.
Ponieważ instrukcja continue bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji continue nigdy nie jest osiągalny.
13.10.4 Instrukcja goto
Instrukcja goto przenosi kontrolkę do instrukcji oznaczonej etykietą.
goto_statement
: 'goto' identifier ';'
| 'goto' 'case' constant_expression ';'
| 'goto' 'default' ';'
;
Celem instrukcji gotoidentyfikatora jest instrukcja oznakowana daną etykietą. Jeśli etykieta o podanej nazwie nie istnieje w aktualnej funkcji lub jeśli instrukcja goto nie znajduje się w zakresie etykiety, pojawi się błąd kompilacji.
Uwaga: Ta reguła zezwala na używanie
gotoinstrukcji 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
gotosł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
gotoopuszcza jeden lub więcej blokówtryze skojarzonymi blokamifinally, kontrola jest początkowo przekazywana do blokufinallynajbardziej wewnętrznej instrukcjitry. Gdy przepływ sterowania osiągnie punkt końcowy blokufinally, zostanie przeniesiony do blokufinallynastępnej otaczającej instrukcjitry. Ten proces jest powtarzany, aż bloki wszystkich interweniującychfinallyinstrukcji zostaną wykonane. - Kontrolka jest przenoszona do elementu docelowego instrukcji
goto.
Ponieważ instrukcja goto bezwarunkowo przenosi kontrolę gdzie indziej, punkt końcowy instrukcji goto nigdy nie jest osiągalny.
13.10.5 Instrukcja return
Instrukcja return zwraca kontrolkę do bieżącego obiektu wywołującego element członkowski funkcji, w którym pojawia się instrukcja return, opcjonalnie zwracając wartość lub variable_reference (§9.5).
return_statement
: 'return' ';'
| 'return' expression ';'
| 'return' 'ref' variable_reference ';'
;
Instrukcja_return bez wyrażenia jest nazywana brak_wartości; ref wyrażenie zawierające wyrażenie jest nazywane zwracanie-przez-referencję, a jedno zawierające tylko wyrażenie jest nazywane zwracanie-przez-wartość.
Jest to błąd czasu kompilacji, jeśli metoda zadeklarowana jako zwracająca przez wartość lub przez referencję nie zwraca żadnej wartości (§15.6.1).
Jest to błąd czasu kompilacji, aby użyć metody return-by-ref zadeklarowanej jako return-no-value lub return-by-value.
Jest to błąd czasu kompilacji, aby użyć zwracania przez wartość w metodzie zadeklarowanej jako nie zwracająca wartości lub zwracająca przez referencję.
Jest to błąd czasu kompilacji, aby użyć wyrażenia return-by-ref, jeśli wyrażenie nie jest variable_reference lub jest odwołaniem do zmiennej, której ref-safe-context nie jest kontekstem wywołującym (§9.7.2).
Jest to błąd czasu kompilacji, aby użyć metody return-by-ref zadeklarowanej przy użyciu method_modifierasync.
Mówi się, że element członkowski funkcji oblicza wartość, jeśli jest metodą zwracającą wartość (§15.6.11), uzyskuje metodę dostępu zwracającą wartość od właściwości lub indeksatora, lub jest operatorem zdefiniowanym przez użytkownika. Elementy członkowskie funkcji, które nie zwracają wartości, nie obliczają wartości i są metodami z efektywnym typem zwrotnym void, ustawnikami właściwości i indeksatorów, dodawnikami i usuwaczami zdarzeń, konstruktorami wystąpień, konstruktorami statycznymi i finalizatorami. Elementy członkowskie funkcji, które są zwracane przez ref, nie obliczają wartości.
W przypadku zwracanej wartości musi istnieć niejawna konwersja (§10.2) z typu wyrażenia do efektywnego typu zwracanego (§15.6.11) członka funkcji zawierającego. W przypadku zwrotu przez referencję, konwersja tożsamości (§10.2.2) musi istnieć między typem wyrażenia a efektywnym typem zwracanym zawierającego elementu członkowskiego funkcji.
returninstrukcje mogą być również używane w treści wyrażeń funkcji anonimowych (§12.21) i uczestniczyć 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. -
returnJeśli instrukcja jest ujęta przez jeden lubtrywięcejcatchbloków ze skojarzonymifinallyblokami, kontrolka jest początkowo przenoszona dofinallybloku najbardziej wewnętrznejtryinstrukcji. Gdy przepływ sterowania osiągnie punkt końcowy blokufinally, zostanie przeniesiony do blokufinallynastępnej otaczającej instrukcjitry. Ten proces jest powtarzany do momentu, aż bloki wszystkich obejmujących instrukcjifinallyzostał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 (§22.4).
W bieżącym elemencie członkowskim funkcji każda
tryinstrukcja, która otacza punkt rzutu, jest badana. Dla każdej instrukcji , począwszy od najbardziej wewnętrznejSinstrukcjitryi kończącej się najbardziej zewnętrznątryinstrukcją, oceniane są następujące kroki:- Jeśli blok
tryobejmuje punkt rzutu i jeśliSma co najmniej jedną klauzulęS, klauzulecatchsą badane w kolejności występowania, aby znaleźć odpowiednią procedurę obsługi dla wyjątku. Pierwszacatchklauzula, 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 wykonywaniaEpochodzi 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 klauzulacatchzawiera filtr wyjątku, ta klauzula jest traktowana jako zgodna, jeśli filtr wyjątkucatchoceni się natrueprawda. 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
trylub blokcatchSotacza punkt rzutu i jeśliSma blokfinally, kontrola jest przekazywana do blokufinally. Jeśli blokfinallyzgł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.Exceptionklasy , chociaż takie wyjątki nigdy nie mogą być generowane przez kod języka C#. Klauzula ogólnacatchmoże służyć do przechwytywania takich wyjątków. W związku z tym klauzula ogólnacatchróż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
catchosią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
Fprzechwytuje 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: GGdyby pierwszy
catchblok rzuciłezamiast ponownego rzucenia bieżącego wyjątku, wygenerowane dane wyjściowe wyglądałyby następująco:Exception in F: G Exception in Main: Fprzykład końcowy
Jest to błąd czasu kompilacji, gdy instrukcja break, continue lub goto przenosi kontrolę poza blok finally. Gdy break, continue lub goto występuje w finally bloku, element docelowy instrukcji musi znajdować się w tym samym finally bloku, w przeciwnym wypadku wystąpi błąd czasu kompilacji.
Jest to błąd czasu kompilacji, jeśli return instrukcja występuje w finally bloku.
Gdy wykonanie osiągnie instrukcję try, sterowanie zostaje przekazane do bloku try. Jeśli kontrola osiągnie końcowy punkt bloku try bez propagacji wyjątku, zostanie przeniesiona do bloku finally, jeśli taki istnieje. Jeśli żaden blok finally nie istnieje, kontrola zostanie przekazana do punktu końcowego instrukcji try.
Jeśli rozpropagowano wyjątek, klauzule, catch jeśli istnieją, są badane w kolejności leksykalnej, szukając pierwszego dopasowania dla wyjątku. Wyszukiwanie klauzuli dopasowania catch jest kontynuowane ze wszystkimi otaczającymi blokami zgodnie z opisem w §13.10.6. Klauzula catch jest zgodna, jeśli typ wyjątku pasuje do dowolnego exception_specifier , a wszystkie exception_filter są prawdziwe. Klauzula catch bez exception_specifier pasuje do dowolnego typu wyjątku. Typ wyjątku jest zgodny z exception_specifier , gdy exception_specifier określa typ wyjątku lub podstawowy typ wyjątku. Jeśli klauzula zawiera filtr wyjątku, obiekt wyjątku jest przypisywany do zmiennej wyjątku, a filtr wyjątku jest obliczany. Jeśli ocena boolean_expression dla exception_filter zgłasza wyjątek, ten wyjątek zostanie przechwycony, a filtr wyjątku zwróci wartość false.
Jeśli został rozpropagowany wyjątek i zostanie znaleziona zgodna catch klauzula, kontrolka zostanie przeniesiona do pierwszego pasującego catch bloku. Jeśli kontrola osiągnie końcowy punkt bloku catch bez propagacji wyjątku, zostanie przeniesiona do bloku finally, jeśli taki istnieje. Jeśli żaden blok finally nie istnieje, kontrola zostanie przekazana do punktu końcowego instrukcji try. Jeśli wyjątek został rozpropagowany z bloku catch, sterowanie przenosi się do bloku finally, jeśli istnieje. Wyjątek jest propagowany do następnej znajdującej się najbliżej instrukcji try.
Jeśli został rozpropagowany wyjątek i nie zostanie znaleziona żadna zgodna catch klauzula, sterowanie zostanie przekazane do bloku finally, jeśli taki istnieje. Wyjątek jest propagowany do następnej znajdującej się najbliżej instrukcji try.
Instrukcje bloku finally są zawsze wykonywane, gdy kontrolka pozostawia instrukcję try. Jest to prawdą, czy transfer kontrolny występuje w wyniku normalnego wykonywania, w wyniku wykonania breakinstrukcji , continue, goto, lub return w wyniku propagacji wyjątku z instrukcji try . Jeśli kontrola osiągnie punkt końcowy bloku finally bez propagacji wyjątku, zostanie przeniesiona do punktu końcowego instrukcji try.
Jeśli podczas wykonywania bloku finally zgłoszony zostanie wyjątek i nie zostanie on przechwycony w tym samym bloku finally, to wyjątek jest propagowany do następnej otaczającej instrukcji try. Jeśli inny wyjątek był w trakcie propagacji, ten wyjątek zostanie utracony. Proces propagowania wyjątku jest omówiony szczegółowo w opisie throw instrukcji (§13.10.6).
Przykład: w poniższym kodzie
public class Test { static void Main() { try { Method(); } catch (Exception ex) when (ExceptionFilter(ex)) { Console.WriteLine("Catch"); } bool ExceptionFilter(Exception ex) { Console.WriteLine("Filter"); return true; } } static void Method() { try { throw new ArgumentException(); } finally { Console.WriteLine("Finally"); } } }metoda
Methodzgłasza wyjątek. Pierwszą akcją jest zbadanie obejmującychcatchklauzul, stosując wszelkie filtry wyjątków. Następnie klauzulafinallywMethodjest wykonywana przed przeniesieniem kontroli do otaczającej pasującej klauzulicatch. Wynikowe dane wyjściowe to:Filter Finally Catchprzykł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
trykońcowy bloku jest osiągalny lub punkt końcowy co najmniej jednegocatchbloku jest osiągalny. - Jeśli blok
finallyjest obecny, punkt końcowy blokufinallyjest 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
13.14.1 Ogólne
Instrukcja using uzyskuje co najmniej jeden zasób, wykonuje instrukcję, a następnie usuwa zasób.
using_statement
: 'await'? 'using' '(' resource_acquisition ')' embedded_statement
;
resource_acquisition
: non_ref_local_variable_declaration
| expression
;
non_ref_local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
;
Typ zasobu jest klasą lub strukturą inną niż ref, która implementuje albo oba System.IDisposable interfejsy lub System.IAsyncDisposable , która zawiera pojedynczą metodę bez parametrów o nazwie Dispose i/lub DisposeAsync; lub struktury ref, która zawiera metodę o nazwie Dispose o takiej samej sygnaturze co zadeklarowana przez System.IDisposable. Kod używający zasobu może wywołać Dispose metodę lub DisposeAsync 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 zasobu. Jeśli forma resource_acquisition jest wyrażeniem , to wyrażenie ma typ zasobu. W await przypadku obecności typ zasobu implementuje System.IAsyncDisposablewartość . Typ ref struct nie może być typem using zasobu dla instrukcji z modyfikatorem await .
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 wyrażenie pozyskiwania zwróci wartość null, nie zostanie wykonane żadne wywołanie Dispose metody (lub DisposeAsync) 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 (lub IAsyncDisposable) podczas pozyskiwania w celu zapewnienia pomyślnej konwersji przed użyciem i likwidacją.
Instrukcja using formularza
using (ResourceType resource = «expression» ) «statement»
odpowiada jednemu z trzech możliwych formuł. W przypadku zasobów klasy i innych niż ref, jeśli ResourceType jest typem wartości innej niż null lub parametrem typu z ograniczeniem typu wartości (§15.2.5), formuła jest semantycznie równoważna
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
((IDisposable)resource).Dispose();
}
}
z wyjątkiem tego, że rzut do resourceSystem.IDisposable nie powoduje wystąpienia boksu.
W przeciwnym razie, gdy ResourceType ma wartość dynamic, formuła jest
{
ResourceType resource = «expression»;
IDisposable d = resource;
try
{
«statement»;
}
finally
{
if (d != null)
{
d.Dispose();
}
}
}
W przeciwnym razie formuła jest
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IDisposable d = (IDisposable)resource;
if (d != null)
{
d.Dispose();
}
}
}
W przypadku zasobów struktury refstrukcyjnej jedynym semantycznie równoważnym sformułowaniem jest
{
«ResourceType» resource = «expression»;
try
{
«statement»;
}
finally
{
resource.Dispose();
}
}
W każdym sformułowaniu zmienna resource jest tylko do odczytu w osadzonej instrukcji, a d zmienna jest niedostępna i niewidoczna dla osadzonej instrukcji.
Oświadczenie using w formie:
using («expression») «statement»
ma te same możliwe formuły.
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
TextWriteriTextReaderimplementują 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
Gdy ResourceType jest typem odwołania, który implementuje IAsyncDisposableelement . Inne formuły do await using wykonywania podobnych podstawień z metody synchronicznej Dispose do metody asynchronicznej DisposeAsync . Deklaracja await using formularza
await using (ResourceType resource = «expression» ) «statement»
jest semantycznie równoważne formułom przedstawionym poniżej IAsyncDisposable zamiast IDisposable, DisposeAsync a nie Dispose, a Task zwracany z DisposeAsync jest await:
await using (ResourceType resource = «expression» ) «statement»
jest semantycznie równoważne
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IAsyncDisposable d = (IAsyncDisposable)resource;
if (d != null)
{
await d.DisposeAsync();
}
}
}
Uwaga: wszelkie instrukcje skoku (§13.10) w embedded_statement muszą być zgodne z rozszerzoną formą
usinginstrukcji. notatka końcowa
13.14.2 Przy użyciu deklaracji
Wariant składniowy instrukcji using jest deklaracją using.
using_declaration
: 'await'? 'using' non_ref_local_variable_declaration ';' statement_list?
;
Deklaracja using ma taką samą semantykę jak i może zostać przepisana jako odpowiednia forma pozyskiwania zasobów instrukcji using (§13.14.1), w następujący sposób:
using «local_variable_type» «local_variable_declarators»
// statements
jest semantycznie równoważne
using («local_variable_type» «local_variable_declarators»)
{
// statements
}
oraz
await using «local_variable_type» «local_variable_declarators»
// statements
jest semantycznie równoważne
await using («local_variable_type» «local_variable_declarators»)
{
// statements
}
Okres istnienia zmiennych zadeklarowanych w non_ref_local_variable_declaration rozciąga się na koniec zakresu, w którym są deklarowane. Te zmienne są następnie usuwane w odwrotnej kolejności, w której są deklarowane.
static void M()
{
using FileStream f1 = new FileStream(...);
using FileStream f2 = new FileStream(...), f3 = new FileStream(...);
...
// Dispose f3
// Dispose f2
// Dispose f1
}
Deklaracja użycia nie pojawia się bezpośrednio wewnątrz switch_label, ale zamiast tego może znajdować się w bloku wewnątrz switch_label.
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 klauzulifinallyinstrukcjitryjest błędem czasu kompilacji. - Jest to błąd czasu kompilacji, gdy instrukcja
yield returnpojawia się w dowolnym miejscu w instrukcjitryzawierają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
Currentwłaściwości obiektu wyliczającego. - Wykonanie bloku iteratora jest zawieszone.
yield returnJeśli instrukcja znajduje się w co najmniej jednymtrybloku, skojarzonefinallybloki nie są wykonywane w tym momencie. -
MoveNextMetoda obiektu modułu wyliczającego powracatruedo 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 breakJeśli instrukcja jest objęta przez co najmniej jedentryblok ze skojarzonymifinallyblokami, sterowanie jest początkowo przenoszone dofinallybloku najgłębszejtryinstrukcji. Gdy przepływ sterowania osiągnie punkt końcowy blokufinally, zostanie przeniesiony do blokufinallynastępnej otaczającej instrukcjitry. Ten proces jest powtarzany do momentu, aż bloki wszystkich obejmujących instrukcjifinallyzostały wykonane. - Kontrolka jest zwracana do obiektu wywołującego blok iteratora. Jest to metoda
MoveNextlub metodaDisposeobiektu 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