Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
13.1 Obecné
Jazyk C# poskytuje různé příkazy.
Poznámka: Většina těchto tvrzení bude známá vývojářům, kteří naprogramovali jazyk C a C++. koncová poznámka
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) a fixed_statement (§24.7) jsou k dispozici pouze v nebezpečném kódu (§24).
Embedded_statement nonterminal se používá pro příkazy, které se zobrazují v jiných příkazech. Použití embedded_statement místo příkazu vylučuje použití deklarací příkazů a příkazů označených v těchto kontextech.
Příklad: Kód
void F(bool b) { if (b) int i = 44; }výsledkem chyby v době kompilace, protože
ifpříkaz vyžaduje embedded_statement místo příkazu pro jehoifvětev. Pokud by byl tento kód povolen, byla by proměnnáideklarována, ale nikdy ji nelze použít. Všimněte si však, že uvedenímideklarace do bloku je příklad platný.konec příkladu
13.2 Koncové body a dosažitelnost
Každý příkaz má koncový bod. Koncovým bodem příkazu je intuitivně umístění, které bezprostředně následuje za příkazem. Pravidla provádění pro složené příkazy (příkazy obsahující vložené příkazy) určují akci, která se provede, když ovládací prvek dosáhne koncového bodu vloženého příkazu.
Příklad: Když ovládací prvek dosáhne koncového bodu příkazu v bloku, ovládací prvek se přenese na další příkaz v bloku. konec příkladu
Pokud je možné dosáhnout příkazu spuštěním, říká se, že je dostupný. Naopak pokud neexistuje možnost, že se příkaz spustí, příkaz je nedostupný.
Příklad: V následujícím kódu
void F() { Console.WriteLine("reachable"); goto Label; Console.WriteLine("unreachable"); Label: Console.WriteLine("reachable"); }Druhé vyvolání console.WriteLine je nedostupné, protože neexistuje možnost, že se příkaz spustí.
konec příkladu
Upozornění se hlásí, pokud není dostupný jiný příkaz než throw_statement, blokování nebo empty_statement . Konkrétně se nejedná o chybu pro nedostupný příkaz.
Poznámka: Chcete-li určit, zda je konkrétní příkaz nebo koncový bod dosažitelný, kompilátor provádí analýzu toku podle pravidel dostupnosti definovaných pro každý příkaz. Analýza toku bere v úvahu hodnoty konstantních výrazů (§12.25), které řídí chování příkazů, ale možné hodnoty ne constantních výrazů se nepovažují. Jinými slovy, pro účely analýzy toku řízení se považuje nekontuční výraz daného typu za možnou hodnotu tohoto typu.
V příkladu
void F() { const int i = 1; if (i == 2) Console.WriteLine("unreachable"); }Logický výraz
ifpříkazu je konstantní výraz, protože oba operandy operátoru==jsou konstanty. Vzhledem k tomu, že konstantní výraz je vyhodnocen v době kompilace, vytvoření hodnotyfalse,Console.WriteLinevyvolání je považováno za nedostupné. Pokudise ale změní na místní proměnnouvoid F() { int i = 1; if (i == 2) Console.WriteLine("reachable"); }
Console.WriteLinevyvolání je považováno za dosažitelné, i když ve skutečnosti se nikdy neprovede.koncová poznámka
Blok člena funkce nebo anonymní funkce je vždy považován za dosažitelný. Následným vyhodnocením pravidel dostupnosti každého příkazu v bloku lze určit dosažitelnost každého daného příkazu.
Příklad: V následujícím kódu
void F(int x) { Console.WriteLine("start"); if (x < 0) Console.WriteLine("negative"); }dosažitelnost druhého
Console.WriteLinese určuje takto:
- První
Console.WriteLinevýraz je dostupný, protože blokFmetody je dosažitelný (§13.3).- Koncový bod prvního
Console.WriteLinevýrazu je dosažitelný, protože tento výrok je dosažitelný (§13.7 a §13.3).- Prohlášení
ifje dosažitelné, protože koncový bod prvníhoConsole.WriteLinevýrazu je dosažitelný (§13.7 a §13.3).- Druhý
Console.WriteLinepříkaz výrazu je dostupný, protože logický výrazifpříkazu nemá konstantní hodnotufalse.konec příkladu
Existují dvě situace, kdy se jedná o chybu v době kompilace pro koncový bod příkazu, aby byl dostupný:
switchVzhledem k tomu, že příkaz nepovoluje přechod oddílu přepínače na další oddíl přepínače, jedná se o chybu v době kompilace pro koncový bod seznamu příkazů oddílu switch, aby byl dostupný. Pokud k této chybě dojde, obvykle to značí, žebreakchybí příkaz.Jedná se o chybu v době kompilace koncového bodu bloku člena funkce nebo anonymní funkce, která vypočítá hodnotu, která bude dosažitelná. Pokud k této chybě dojde, obvykle je to označení, že
returnchybí prohlášení (§13.10.5).
13.3 Bloky
13.3.1 Obecné
Blok umožňuje zápis více příkazů v kontextech, kde je povolený jeden příkaz.
block
: '{' statement_list? '}'
;
Blok se skládá z volitelného statement_list (§13.3.2) uzavřených ve složených závorkách. Pokud seznam příkazů vynecháte, bude blok prázdný.
Blok může obsahovat prohlášení (§13.6). Obor místní proměnné nebo konstanty deklarované v bloku je blok.
Blok se spustí takto:
- Pokud je blok prázdný, ovládací prvek se přenese na koncový bod bloku.
- Pokud blok není prázdný, ovládací prvek se přenese do seznamu příkazů. Když a pokud ovládací prvek dosáhne koncového bodu seznamu příkazů, ovládací prvek se přenese na koncový bod bloku.
Seznam příkazů bloku je dostupný, pokud je samotný blok dostupný.
Koncový bod bloku je dostupný, pokud je blok prázdný nebo pokud je koncový bod seznamu příkazů dostupný.
Blok, který obsahuje jeden nebo více yield tvrzení (§13.15), se nazývá blok iterátoru. Bloky iterátoru slouží k implementaci členů funkce jako iterátorů (§15.15). Některá další omezení platí pro bloky iterátoru:
- Jedná se o chybu v době kompilace, kdy se
returnpříkaz zobrazí v bloku iterátoru (aleyield returnpříkazy jsou povolené). - Jedná se o chybu v době kompilace, kdy blok iterátoru obsahuje nebezpečný kontext (§24.2). Blok iterátoru vždy definuje bezpečný kontext, i když je jeho deklarace vnořena do nebezpečného kontextu.
13.3.2 Seznamy příkazů
Seznam příkazů se skládá z jednoho nebo více příkazů zapsaných v posloupnosti. Seznamy prohlášení se vyskytují v bloku s (§13.3) a v switch_blocks (§13.8.3).
statement_list
: statement+
;
Seznam příkazů se provádí přenosem ovládacího prvku na první příkaz. Když a pokud ovládací prvek dosáhne koncového bodu příkazu, ovládací prvek se přenese na další příkaz. Když a pokud ovládací prvek dosáhne koncového bodu posledního příkazu, ovládací prvek se přenese na koncový bod seznamu příkazů.
Příkaz v seznamu příkazů je dostupný, pokud platí alespoň jedna z následujících možností:
- Příkaz je první příkaz a samotný seznam příkazů je dostupný.
- Koncový bod předchozího příkazu je dostupný.
- Příkaz je označený příkazem a popisek je odkazován dosažitelným
gotopříkazem.
Koncový bod seznamu příkazů je dostupný, pokud je dostupný koncový bod posledního příkazu v seznamu.
13.4 Prázdný příkaz
Empty_statement nic nedělá.
empty_statement
: ';'
;
Prázdný příkaz se používá v případě, že neexistují žádné operace, které by bylo možné provést v kontextu, kde je příkaz povinný.
Spuštění prázdného příkazu jednoduše přenese řízení na koncový bod příkazu. Koncový bod prázdného příkazu je tak dostupný, pokud je prázdný příkaz dostupný.
Příklad: Prázdný příkaz lze použít při zápisu
whilepříkazu s tělem null:bool ProcessMessage() {...} void ProcessMessages() { while (ProcessMessage()) ; }Prázdný příkaz lze také použít k deklaraci popisku těsně před uzavřením
}"" bloku:void F(bool done) { ... if (done) { goto exit; } ... exit: ; }konec příkladu
13.5 Příkazy s popisky
Labeled_statement umožňuje, aby byl příkaz předponou popisku. Příkazy s popisky jsou povoleny v blocích, ale nejsou povoleny jako vložené příkazy.
labeled_statement
: identifier ':' statement
;
Příkaz s popiskem deklaruje popisek s názvem zadaným identifikátorem. Rozsah popisku je celý blok, ve kterém je popisek deklarován, včetně všech vnořených bloků. Jedná se o chybu v době kompilace pro dva popisky se stejným názvem, aby měly překrývající se obory.
Popisek lze odkazovat z goto prohlášení (§13.10.4) v rámci popisku.
Poznámka: To znamená, že
gotopříkazy mohou přenášet řízení v blocích a mimo bloky, ale nikdy do bloků. koncová poznámka
Popisky mají vlastní prostor deklarací a neruší jiné identifikátory.
Příklad: Příklad
int F(int x) { if (x >= 0) { goto x; } x = -x; x: return x; }je platný a používá název x jako parametr i popisek.
konec příkladu
Provedení příkazu označeného popiskem přesně odpovídá provedení příkazu za popiskem.
Kromě dosažitelnosti poskytované normálním tokem řízení je popisek dostupný, pokud je popisek odkazován dosažitelným goto příkazem, pokud goto není příkaz uvnitř try bloku nebo catch bloku try_statement , který obsahuje finally blok, jehož koncový bod je nedostupný, a popisek je mimo try_statement.
13.6 Prohlášení
13.6.1 Obecné
Declaration_statement deklaruje jednu nebo více místních proměnných, jednu nebo více místních konstant nebo místní funkci. Příkazy deklarace jsou povoleny v blocích a blocích switch, ale nejsou povoleny jako vložené příkazy.
declaration_statement
: local_variable_declaration ';'
| local_constant_declaration ';'
| local_function_declaration
;
Místní proměnná je deklarována pomocí local_variable_declaration (§13.6.2). Místní konstanta je deklarována pomocí local_constant_declaration (§13.6.3). Místní funkce je deklarována pomocí local_function_declaration (§13.6.4).
Deklarované názvy se zavádějí do nejbližšího ohraničujícího prostoru deklarace (§7.3).
13.6.2 Deklarace místních proměnných
13.6.2.1 Obecné
Local_variable_declaration deklaruje jednu nebo více místních proměnných.
local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
| explicitly_typed_ref_local_variable_declaration
;
Implicitně zadané deklarace obsahují kontextové klíčové slovo (§6.4.4), var což vede k syntaktické nejednoznačnosti mezi třemi kategoriemi, které jsou vyřešeny následujícím způsobem:
- Pokud neexistuje žádný typ pojmenovaný
varv oboru a vstup odpovídá implicitly_typed_local_variable_declaration je zvolen; - Jinak pokud je název typu
varv oboru, implicitly_typed_local_variable_declaration se nepovažuje za možnou shodu.
V rámci local_variable_declaration každá proměnná je zavedena deklarátorem, což je jeden z implicitly_typed_local_variable_declarator, explicitly_typed_local_variable_declarator nebo ref_local_variable_declarator pro implicitně napsané, explicitně zadané a ref místní proměnné. Deklarátor definuje název (identifikátor) a počáteční hodnotu zavedené proměnné( pokud existuje).
Pokud v deklaraci existuje více deklarátorů, zpracovávají se, včetně inicializačních výrazů, v pořadí odleva doprava (§9.4.4.5).
Poznámka: U local_variable_declaration , ke kterému nestává for_initializer (§13.9.4) nebo resource_acquisition (§13.14) se toto pořadí zleva doprava rovná každému deklarátoru, který je v samostatném local_variable_declaration. Příklad:
void F() { int x = 1, y, z = x * 2; }odpovídá:
void F() { int x = 1; int y; int z = x * 2; }koncová poznámka
Hodnota místní proměnné se získá ve výrazu pomocí simple_name (§12.8.4). Místní proměnná musí být rozhodně přiřazena (§9.4) v každém místě, kde je získána jeho hodnota. Každá místní proměnná zavedená local_variable_declaration je zpočátku nepřiřazena (§9.4.3). Pokud deklarátor má inicializační výraz, je zavedená místní proměnná klasifikována jako přiřazená na konci deklarátoru (§9.4.4.5).
Rozsah místní proměnné zavedené local_variable_declaration je definován takto (§7.7):
- Pokud k prohlášení dojde jako for_initializer pak je rozsahem for_initializer, for_condition, for_iterator a embedded_statement (§13.9.4);
- Pokud se prohlášení vyskytuje jako resource_acquisition pak je rozsahem nejkrajnější blok sémanticky ekvivalentního rozšíření using_statement (§13.14);
- V opačném případě je obor blokem, ve kterém se deklarace vyskytuje.
Jedná se o chybu odkazující na místní proměnnou podle názvu v textové pozici, která předchází deklarátoru nebo v rámci jakéhokoli inicializačního výrazu v deklarátoru. V oboru místní proměnné se jedná o chybu v době kompilace, která deklaruje jinou místní proměnnou, místní funkci nebo konstantu se stejným názvem.
Kontext ref-safe-context (§9.7.2) místní proměnné ref je kontext ref-safe jeho inicializace variable_reference. Kontext ref-safe-context místních proměnných bez odkazu je blok deklarace.
13.6.2.2 Implicitně zadané deklarace místních proměnných
implicitly_typed_local_variable_declaration
: 'var' implicitly_typed_local_variable_declarator
| ref_kind 'var' ref_local_variable_declarator
;
implicitly_typed_local_variable_declarator
: identifier '=' expression
;
Implicitly_typed_local_variable_declaration zavádí jednu místní proměnnou, identifikátor. Výraz nebo variable_reference musí mít typ kompilačního času . T První alternativa deklaruje proměnnou s počáteční hodnotou výrazu; její typ je T? v případě, že T je nenulový odkazový typ, jinak je Tjeho typ . Druhá alternativa deklaruje proměnnou ref s počáteční hodnotou refvariable_reference; její typ je ref T? v případě, že T se jedná o nenulový odkazový typ, jinak je ref Tjeho typ . (ref_kind je popsáno v §15.6.1.)
Příklad:
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;Výše uvedené deklarace místní proměnné implicitně odpovídají následujícím explicitně zadaným deklaracím:
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;Následující jsou nesprávné implicitně zadané deklarace místních proměnných:
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 itselfkonec příkladu
13.6.2.3 Explicitně zadané deklarace místních proměnných
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 zavádí jednu nebo více místních proměnných se zadaným typem.
Je-li k dispozici local_variable_initializer , musí být jeho typ vhodný podle pravidel jednoduchého přiřazení (§12.23.2) nebo inicializace pole (§17.7) a jeho hodnota je přiřazena jako počáteční hodnota proměnné.
13.6.2.4 Explicitně zadané deklarace místních proměnných odkaz
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
;
Inicializační variable_reference musí mít typ a musí splňovat stejné požadavky jako pro přiřazení odkazu (§12.23.3).
Pokud je ref_kindref readonly, deklarované identifikátory jsou odkazy na proměnné, které jsou považovány za pouze pro čtení. V opačném případě, pokud jerefref_kind , jsou deklarované identifikátoryodkazy na proměnné, které musí být zapisovatelné.
Jedná se o chybu při kompilaci deklarovat referenční místní proměnnou nebo proměnnou typu ref struct v rámci metody deklarované pomocí method_modifierasync nebo v rámci iterátoru (§15.15).
13.6.3 Deklarace místních konstant
Local_constant_declaration deklaruje jednu nebo více místních konstant.
local_constant_declaration
: 'const' type constant_declarators
;
constant_declarators
: constant_declarator (',' constant_declarator)*
;
constant_declarator
: identifier '=' constant_expression
;
Typ local_constant_declaration určuje typ konstant zavedených deklarací. Za typem následuje seznam constant_declarators, z nichž každá představuje novou konstantu.
Constant_declarator se skládá z identifikátoru, který pojmenuje konstantu následovanou tokenem "=", za kterým následuje constant_expression (§12,25), který dává hodnotu konstanty.
Typ a constant_expression místní deklarace konstanty se řídí stejnými pravidly jako deklarace konstantního členu (§15.4).
Hodnota místní konstanty je získána ve výrazu pomocí simple_name (§12.8.4).
Obor místní konstanty je blok, ve kterém se deklarace vyskytuje. Jedná se o chybu odkazující na místní konstantu v textové pozici, která předchází konci jeho constant_declarator.
Místní deklarace konstanty, která deklaruje více konstant, je ekvivalentní více deklarací jednoduchých konstant se stejným typem.
13.6.4 Deklarace místní funkce
Local_function_declaration deklaruje místní funkci.
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 ';'
;
Gramatická poznámka: Při uznání local_function_body, pokud platí jak null_conditional_invocation_expression, tak alternativy výrazu, pak se zvolí první možnost. (§15.6.1)
Příklad: Pro místní funkce existují dva běžné případy použití: metody iterátoru a asynchronní metody. V metodách iterátoru jsou všechny výjimky pozorovány pouze při volání kódu, který vyčísluje vrácenou sekvenci. V asynchronníchmetodch Následující příklad ukazuje oddělení ověření parametru od implementace iterátoru pomocí místní funkce:
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; } } }konec příkladu
Pokud není uvedeno jinak, sémantika všech gramatických prvků je stejná jako u method_declaration (§15.6.1), přečteno v kontextu místní funkce místo metody.
Identifikátor local_function_declaration musí být v deklarovaném rozsahu bloku jedinečný, včetně všech uzavřených prostorů deklarace místních proměnných. Jedním z důsledků je to, že přetížené local_function_declarations nejsou povoleny.
Local_function_declaration může obsahovat jeden async modifikátor (§15.14) a jeden unsafe modifikátor (§24.1). Pokud deklarace obsahuje modifikátor async, měl by návratový typ být void nebo typu «TaskType» (§15.14.1). Pokud deklarace obsahuje static modifikátor, je to statická místní funkce, jinak se jedná o nestatické místní funkce. Jedná se o chybu v době kompilace pro type_parameter_list nebo parameter_list, která obsahuje atributy. Pokud je místní funkce deklarována v nebezpečném kontextu (§24.2), místní funkce může obsahovat nebezpečný kód, i když deklarace místní funkce neobsahuje unsafe modifikátor.
Místní funkce je deklarována v oboru bloku. Nestatická místní funkce může zachytit proměnné z nadřazeného oboru, zatímco statická místní funkce nesmí (takže nemá přístup k uzavření místních hodnot, parametrů, nestatických místních funkcí nebo this). Jedná se o chybu v době kompilace, pokud zachycená proměnná čte tělo nestatické místní funkce, ale není rozhodně přiřazena před každým voláním funkce. Kompilátor určí, které proměnné jsou jednoznačně přiřazeny při vrácení (§9.4.4.33).
Pokud je typ this struktury typu, jedná se o chybu kompilace těla místní funkce pro přístup this. To platí, zda je přístup explicitní (jako v this.x) nebo implicitní (jako v případě, kde xx je členem instance struktury). Toto pravidlo zakáže pouze takový přístup a nemá vliv na to, zda vyhledávání členů vede k členu struktury.
Jedná se o chybu v době kompilace pro tělo místní funkce, která obsahuje goto příkaz, break příkaz nebo continue příkaz, jehož cíl je mimo tělo místní funkce.
Poznámka: výše uvedená pravidla pro
thisanonymnígotofunkce a zrcadlí je v §12.21.3. koncová poznámka
Místní funkce může být volána z lexikálního bodu před jeho deklarací. Jedná se však o chybu v době kompilace, která má být deklarována lexicky před deklarací proměnné použité v místní funkci (§7.7).
Jedná se o chybu v době kompilace pro místní funkci, která deklaruje parametr, parametr typu nebo místní proměnnou se stejným názvem, který je deklarován v jakémkoli uzavřeném prostoru deklarace místní proměnné.
Místní těla funkcí jsou vždy dosažitelná. Koncový bod deklarace místní funkce je dostupný, pokud je dostupný počáteční bod deklarace místní funkce.
Příklad: V následujícím příkladu je tělo
Ldosažitelné, i když počáteční bodLnení dosažitelný. Vzhledem k tomu, že počáteční bodLnení dostupný, příkaz následující za koncovým bodemLnení dostupný: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; } }Jinými slovy, umístění deklarace místní funkce nemá vliv na dosažitelnost žádných příkazů v obsahující funkci. konec příkladu
Pokud je dynamictyp argumentu pro místní funkci , funkce, která má být volána, musí být vyřešena v době kompilace, nikoli za běhu.
Místní funkce se nesmí používat ve stromu výrazů.
Statická místní funkce
- Může odkazovat na statické členy, parametry typu, definice konstant a statické místní funkce z nadřazeného oboru.
- Nesmí odkazovat
thisanibasena členy instancí z implicitníhothisodkazu ani z místních proměnných, parametrů nebo nestatických místních funkcí z nadřazeného oboru. Všechny tyto možnosti jsou však ve výrazunameof()povolené.
13.7 Příkazy výrazů
Expression_statement vyhodnotí daný výraz. Hodnota vypočítaná výrazem (pokud existuje) se zahodí.
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
;
Ne všechny výrazy jsou povolené jako příkazy.
Poznámka: Zejména výrazy
x + yjako ax == 1, které pouze vypočítávají hodnotu (která bude zahozena), nejsou povoleny jako příkazy. koncová poznámka
Spuštění expression_statement vyhodnotí obsažený výraz a pak přenese řízení na koncový bod expression_statement. Koncový bod expression_statement je dostupný, pokud je tato expression_statement dosažitelná.
13.8 Příkazy výběru
13.8.1 Obecné
Příkazy výběru vyberou jeden z několika možných příkazů pro spuštění na základě hodnoty některého výrazu.
selection_statement
: if_statement
| switch_statement
;
13.8.2 Příkaz if
Příkaz if vybere příkaz pro spuštění na základě hodnoty logického výrazu.
if_statement
: 'if' '(' boolean_expression ')' embedded_statement
| 'if' '(' boolean_expression ')' embedded_statement
'else' embedded_statement
;
Část else je přidružena k lexicky nejbližšímu předchozímu if , který je povolen syntaxí.
Příklad: Proto příkaz
ifformulářeif (x) if (y) F(); else G();je ekvivalentem
if (x) { if (y) { F(); } else { G(); } }konec příkladu
Příkaz if se provede následujícím způsobem:
- Vyhodnocuje se boolean_expression (§12.26).
- Pokud logický výraz vrátí
truehodnotu , ovládací prvek se přenese na první vložený příkaz. Když a pokud ovládací prvek dosáhne koncového bodu tohoto příkazu, ovládací prvek se přenese na koncový bodifpříkazu. - Pokud logický výraz získá
falsehodnotu a pokudelseje součástí, ovládací prvek se přenese do druhého vloženého příkazu. Když a pokud ovládací prvek dosáhne koncového bodu tohoto příkazu, ovládací prvek se přenese na koncový bodifpříkazu. - Pokud logický výraz vrátí
falsehodnotu a pokudelsečást není k dispozici, ovládací prvek se převede na koncový bodifpříkazu.
První vložený příkaz if příkazu je dostupný, pokud if je příkaz dostupný a logický výraz nemá konstantní hodnotu false.
Druhý vložený příkaz if příkazu, pokud je k dispozici, je dosažitelný, pokud if je příkaz dostupný a logický výraz nemá konstantní hodnotu true.
Koncový bod if příkazu je dosažitelný, pokud je dostupný koncový bod alespoň jednoho z jeho vložených příkazů. Koncový bod if příkazu bez části else je navíc dosažitelný, pokud if je příkaz dostupný a logický výraz nemá konstantní hodnotu true.
13.8.3 Příkaz switch
Příkaz switch vybere pro spuštění seznam příkazů s přidruženým popiskem přepínače, který odpovídá hodnotě selector_expression přepínače.
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 se skládá z klíčového slova switch, následovaného výrazem tuple_expression nebo závorkou (každý z nich se nazývá selector_expression), za kterým následuje switch_block. Switch_block se skládá z nuly nebo více switch_sections uzavřených ve složených závorkách. Každý switch_section se skládá z jednoho nebo více switch_labelnásledovaných statement_list (§13.3.2). Každý switch_label obsahující case má přidružený vzor (§11), proti kterému se testuje hodnota selector_expression přepínače. Je-li case_guard přítomna, jeho výraz se implicitně konvertibilní na typ bool a tento výraz se vyhodnotí jako další podmínka, aby byl případ považován za splněný.
Poznámka: Pro usnadnění je možné vynechat závorky v switch_statement , pokud je selector_expressiontuple_expression. Může být například
switch ((a, b)) …zapsán jakoswitch (a, b) …. koncová poznámka
Typ řízeníswitch příkazu je vytvořen selector_expression přepínače.
- Pokud typ selector_expression přepínače je
sbyte,byte,intshortuintushortlong, ,ulong,char,boolnebostringenum_type, nebo pokud je typ hodnoty null, který odpovídá jednomu z těchto typů, pak se jedná o řídicí typswitchpříkazu. - V opačném případě, pokud přesně jeden uživatelem definovaný implicitní převod existuje z typu přepínače selector_expression na jeden z následujících možných řídicích typů:
sbyte, ,intbyteshortuintushort,ulonglong,charstringnebo, nullable typ hodnoty odpovídající jednomu z těchto typů, pak převedený typ je řídící typswitchpříkazu. - V opačném případě je řídicím typem
switchpříkazu typ selector_expression přepínače. Pokud takový typ neexistuje, jedná se o chybu.
Příkaz může obsahovat maximálně jeden default popisek switch .
Jedná se o chybu, pokud vzor jakéhokoli popisku přepínače není použitelný (§11.2.1) pro typ vstupního výrazu.
Jedná se o chybu, pokud je vzor jakéhokoli popisku přepínače dílčím součtem (§11.3) množinou vzorů dřívějších popisků přepínače příkazu přepínače, které nemají kryt případu nebo jejichž případová ochrana je konstantní výraz s hodnotou true.
Příklad:
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. }konec příkladu
Příkaz switch se provede následujícím způsobem:
- Selector_expression přepínače se vyhodnotí a převede na typ řízení.
- Ovládací prvek se přenese podle hodnoty selector_expression převedeného přepínače:
- Lexicky první vzor v sadě
casepopisků ve stejnémswitchpříkazu, který odpovídá hodnotě selector_expression přepínače, a pro který výraz stráže chybí nebo je vyhodnocen jako true, způsobí, že ovládací prvek se přenese do seznamu příkazů za odpovídajícímcasepopiskem. - Pokud je popisek k dispozici,
defaultovládací prvek se přenese do seznamu příkazů zadefaultpopiskem. - Jinak se ovládací prvek přenese na koncový bod
switchpříkazu.
- Lexicky první vzor v sadě
Poznámka: Pořadí, ve kterém se vzory shodují za běhu, není definováno. Kompilátor je povolený (ale nevyžaduje) porovnávání vzorů mimo pořadí a opakované použití výsledků již spárovaných vzorů k výpočtu výsledku porovnávání jiných vzorů. Kompilátor je však nutný k určení prvního vzoru z hlediska lexikální, který odpovídá výrazu a pro nějž zabezpečovací podmínka buď chybí, nebo se vyhodnotí jako
true. koncová poznámka
Pokud je koncový bod seznamu příkazů oddílu přepínače dostupný, dojde k chybě v době kompilace. To se označuje jako pravidlo "žádný pád".
Příklad: Příklad
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; default: CaseOthers(); break; }je platný, protože žádný oddíl přepínače nemá dosažitelný koncový bod. Na rozdíl od jazyka C a C++ není spuštění oddílu přepínače povoleno "projít" do dalšího oddílu přepínače a příklad
switch (i) { case 0: CaseZero(); case 1: CaseZeroOrOne(); default: CaseAny(); }výsledkem je chyba v době kompilace. Je-li provedení oddílu přepínače následované provedením jiného oddílu přepínače, použije se explicitní
goto casenebogoto defaultprohlášení:switch (i) { case 0: CaseZero(); goto case 1; case 1: CaseZeroOrOne(); goto default; default: CaseAny(); break; }konec příkladu
V switch_section je povoleno více popisků.
Příklad: Příklad
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; case 2: default: CaseTwo(); break; }je platný. Příklad neporušuje pravidlo "žádný pád", protože popisky
case 2:adefault:jsou součástí stejného switch_section.konec příkladu
Poznámka: Pravidlo "bez propadnutí" zabraňuje běžné třídě chyb, ke kterým dochází v jazyce C a C++ při
breaknáhodném vynechání příkazů. Například oddíly výše uvedenéhoswitchpříkazu se dají vrátit zpět, aniž by to ovlivnilo chování příkazu:switch (i) { default: CaseAny(); break; case 1: CaseZeroOrOne(); goto default; case 0: CaseZero(); goto case 1; }koncová poznámka
Poznámka: Seznam příkazů oddílu switch obvykle končí na
break,goto casenebogoto defaultpříkaz, ale jakýkoli konstruktor, který vykreslí koncový bod seznamu příkazů nedostupný, je povolen. Napříkladwhilepříkaz řízený logickým výrazemtruese nikdy nedosahuje koncového bodu. Podobně příkazthrownebo příkazreturnvždy přenáší řízení jinde a nikdy nedosáhne svého koncového bodu. Následující příklad je tedy platný:switch (i) { case 0: while (true) { F(); } case 1: throw new ArgumentException(); case 2: return; }koncová poznámka
Příklad: Typ
switchřízení příkazu může být typstring. Příklad:void DoCommand(string command) { switch (command.ToLower()) { case "run": DoRun(); break; case "save": DoSave(); break; case "quit": DoQuit(); break; default: InvalidCommand(command); break; } }konec příkladu
Poznámka: Podobně jako operátory rovnosti řetězců (§12.14.8)
switchse v příkazu rozlišují malá a velká písmena a provede daný oddíl přepínače pouze v případě, že řetězec přepínače selector_expression přesně odpovídá konstantěcasepopisku. koncová poznámka
Pokud je string řídicí typ switch příkazu nebo typ hodnoty null, je tato hodnota null povolena jako konstanta case popisku.
Statement_list switch_block mohou obsahovat prohlášení (§13.6). Obor místní proměnné nebo konstanty deklarované v bloku přepínače je blok přepínače.
Popisek přepínače je dostupný, pokud platí alespoň jedna z následujících možností:
- Selector_expression přepínače je konstantní hodnota a buď
- popisek je
caseštítek, jehož vzor by se shodoval (§11.2.1), a stráž popisku buď chybí, nebo není konstantní výraz s hodnotou false; - je
defaultto popisek a žádný oddíl přepínače neobsahuje popisek případu, jehož vzor by odpovídal dané hodnotě, a jehož stráž chybí nebo konstantní výraz s hodnotou true.
- popisek je
-
Selector_expression přepínače není konstantní hodnota a ani
- popisek je
casebez stráže nebo s stráží, jejíž hodnota není konstantní nepravda; nebo - je
defaultto popisek a- sada vzorů, které se objevují v případech příkazu přepínače, které nemají stráže nebo mají stráže, jejichž hodnota je konstantní pravda, není vyčerpávající (§11.4) pro typ řízení přepínače; nebo
- přepínač řídící typ je typ s možnou hodnotou null a sada vzorů, které se zobrazují mezi případy příkazu switch, které nemají stráže nebo mají stráže, jejichž hodnota je konstanta true neobsahuje vzor, který by odpovídal hodnotě
null.
- popisek je
- Popisek přepínače je odkazován dosažitelným
goto casenebogoto defaultpříkazem.
Seznam příkazů daného oddílu přepínače je dostupný, pokud switch je příkaz dostupný a oddíl přepínače obsahuje popisek dosažitelného přepínače.
Koncový bod switch příkazu je dostupný, pokud je příkaz switch dostupný a alespoň jedna z následujících hodnot je pravdivá:
- Příkaz
switchobsahuje dostupnýbreakpříkaz, který příkaz ukončíswitch. - Žádný
defaultpopisek není k dispozici a ani- Selector_expression přepínače je nekontinutní hodnota a sada vzorů, které se objevují mezi případy příkazu přepínače, které nemají stráže nebo mají stráže, jejichž hodnota je konstantní, není vyčerpávající (§11.4) pro typ řízení přepínače.
-
Selector_expression přepínače je ne konstantní hodnota typu s možnou hodnotou null a mezi případy příkazu switch, které nemají stráže nebo mají stráže, jejichž hodnota je konstanta true, by odpovídala hodnotě
null. - Selector_expression přepínače je konstantní hodnota a žádný
casepopisek bez stráže nebo hlídač je konstanta true by odpovídala této hodnotě.
Příklad: Následující kód ukazuje stručné použití
whenklauzule: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"; } }Velikost písmen var odpovídá
nullprázdnému řetězci nebo libovolnému řetězci, který obsahuje pouze prázdné znaky. konec příkladu
13.9 Příkazy iterace
13.9.1 Obecné
Příkazy iterace opakovaně spouštějí vložený příkaz.
iteration_statement
: while_statement
| do_statement
| for_statement
| foreach_statement
;
13.9.2 Příkaz while
Příkaz while podmíněně spustí vložený příkaz nulou nebo vícekrát.
while_statement
: 'while' '(' boolean_expression ')' embedded_statement
;
Příkaz while se provede následujícím způsobem:
- Vyhodnocuje se boolean_expression (§12.26).
- Pokud logický výraz vrátí hodnotu
true, ovládací prvek se přenese do vloženého příkazu. Když a pokud ovládací prvek dosáhne koncového bodu vloženého příkazu (pravděpodobně ze spuštěnícontinuepříkazu), ovládací prvek se přenese na začátekwhilepříkazu. - Pokud logický výraz vrátí
falsehodnotu , ovládací prvek se převede na koncový bodwhilepříkazu.
V rámci vloženého prohlášení while prohlášení break lze k převodu řízení na koncový bod příkazu použít příkaz (while) a continue příkaz (§13.10.3) k převodu řízení na koncový bod vloženého příkazu (tedy k provedení další iterace while příkazu).
Vložený příkaz while příkazu je dostupný, pokud while je příkaz dostupný a logický výraz nemá konstantní hodnotu false.
Koncový bod while prohlášení je dosažitelný, pokud platí alespoň jedna z následujících možností:
- Příkaz
whileobsahuje dostupnýbreakpříkaz, který příkaz ukončíwhile. - Příkaz
whileje dostupný a logický výraz nemá konstantní hodnotutrue.
13.9.3 Příkaz do
Příkaz do podmíněně spustí vložený příkaz jednou nebo vícekrát.
do_statement
: 'do' embedded_statement 'while' '(' boolean_expression ')' ';'
;
Příkaz do se provede následujícím způsobem:
- Ovládací prvek se přenese do vloženého příkazu.
- Pokud a pokud ovládací prvek dosáhne koncového bodu vloženého příkazu (pravděpodobně ze spuštění
continuepříkazu), vyhodnotí se boolean_expression (§12.26). Pokud logický výraz vrátítruehodnotu , ovládací prvek se převede na začátekdopříkazu. Jinak se ovládací prvek přenese na koncový boddopříkazu.
V rámci vloženého prohlášení do prohlášení break lze k převodu řízení na koncový bod příkazu použít příkaz (do) a continue příkaz (§13.10.3) k převodu řízení na koncový bod vloženého příkazu (tedy k provedení další iterace do příkazu).
Vložený příkaz do příkazu je dostupný, pokud do je příkaz dostupný.
Koncový bod do prohlášení je dosažitelný, pokud platí alespoň jedna z následujících možností:
- Příkaz
doobsahuje dostupnýbreakpříkaz, který příkaz ukončído. - Koncový bod vloženého příkazu je dostupný a logický výraz nemá konstantní hodnotu
true.
13.9.4 Příkaz for
Příkaz for vyhodnotí sekvenci inicializačních výrazů a poté, když je podmínka pravdivá, opakovaně spustí vložený příkaz a vyhodnotí posloupnost výrazů iterace.
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, pokud existují, se skládá z local_variable_declaration (§13.6.2) nebo seznamu statement_expressions (§13.7) oddělených čárkami. Obor místní proměnné deklarované for_initializer je for_initializer, for_condition, for_iterator a embedded_statement.
For_condition, pokud existuje, je boolean_expression (§12.26).
For_iterator, pokud existují, se skládá ze seznamu statement_expressions (§13.7) oddělených čárkami.
Příkaz for se provede následujícím způsobem:
- Pokud existuje for_initializer, inicializátory proměnných nebo výrazy příkazů se spustí v pořadí, ve kterém jsou zapsány. Tento krok se provádí pouze jednou.
- Pokud existuje for_condition, vyhodnotí se.
-
Pokud for_condition není k dispozici nebo pokud je vyhodnocení výnosu
true, ovládací prvek je převeden na vložený příkaz. Když a pokud ovládací prvek dosáhne koncového bodu vloženého příkazu (pravděpodobně z prováděnícontinuepříkazu), výrazy for_iterator, pokud existují, se vyhodnocují v posloupnosti a pak se provede další iterace, počínaje vyhodnocením for_condition v kroku výše. -
Pokud je for_condition přítomna a vyhodnocení se provede
false, řízení se přenese na koncový bodforpříkazu.
V rámci vloženého prohlášení lze příkaz (for) použít k převodu řízení na koncový bod break příkazu (tedy k ukončení iterace vloženého příkazu) a k převodu řízení na koncový bod vloženého příkazu (tedy k provádění for a provádění další iterace continue příkazu, lze použít příkaz (§13.10.3).for počínaje for_condition).
Vložený příkaz for příkazu je dostupný, pokud je splněna jedna z následujících možností:
- Příkaz
forje dostupný a není k dispozici žádná for_condition . - Příkaz
forje dostupný a for_condition je přítomen a nemá konstantní hodnotufalse.
Koncový bod for prohlášení je dosažitelný, pokud platí alespoň jedna z následujících možností:
- Příkaz
forobsahuje dostupnýbreakpříkaz, který příkaz ukončífor. - Příkaz
forje dostupný a for_condition je přítomen a nemá konstantní hodnotutrue.
13.9.5 Příkaz foreach
13.9.5.1 Obecné
Příkaz foreach vyčíslí prvky kolekce a spustí vložený příkaz pro každý prvek kolekce.
foreach_statement
: 'await'? 'foreach' '(' ref_kind? local_variable_type identifier
'in' expression ')' embedded_statement
;
Local_variable_type a identifikátor příkazu foreach deklaruje proměnnou iterace příkazu.
var Pokud je identifikátor uveden jako local_variable_type a žádný typ pojmenovaný var není v oboru, iterační proměnná je označena jako implicitně zadaná iterační proměnná a jeho typ je považován za typ foreach prvku příkazu, jak je uvedeno níže.
Jedná se o chybu v době kompilace, pokud jsou await a ref_kind přítomny v foreach statement.
Pokud foreach_statement obsahuje obojí nebo ani jednoref, readonlyiterační proměnná označuje proměnnou, která je považována za jen pro čtení. Jinak pokud foreach_statement obsahuje ref bez readonly, iterační proměnná označuje proměnnou, která se má zapisovat.
Iterační proměnná odpovídá místní proměnné s oborem, který se vztahuje na vložený příkaz. Během provádění foreach příkazu představuje proměnná iterace prvek kolekce, pro který se právě provádí iterace. Pokud iterační proměnná označuje proměnnou určenou jen pro čtení, dojde k chybě v době kompilace, pokud se vložený příkaz pokusí ho upravit (prostřednictvím přiřazení nebo ++-- operátorů) nebo ho předat jako odkaz nebo výstupní parametr.
Zpracování kompilace foreach příkazu nejprve určuje typ kolekce (C), typ enumerátoru (E) a typ iterace (Tnebo ref Tref readonly T) výrazu.
Stanovení je podobné pro synchronní a asynchronní verze. Různá rozhraní s různými metodami a návratové typy rozlišují synchronní a asynchronní verze. Obecný proces pokračuje následujícím způsobem. Názvy v rámci '«' a '»' jsou zástupné symboly pro skutečné názvy synchronních a asynchronních iterátorů. Typy povolené pro «GetEnumerator», «MoveNext», «IEnumerable»T, «IEnumerator»<T> a všechny další rozdíly jsou podrobně popsány v < pro synchronní > příkaz a v §13.9.5.3 pro asynchronní foreach příkaz.foreach
- Určete, zda typ
Xvýrazu má odpovídající metodu «GetEnumerator»:- Proveďte vyhledávání členů u typu
Xs identifikátorem «GetEnumerator» a bez argumentů typu. Pokud vyhledávání členů nevytváří shodu nebo vytvoří nejednoznačnost nebo vytvoří shodu, která není skupinou metod, zkontrolujte výčtové rozhraní, jak je popsáno v kroku 2. Doporučuje se vydat upozornění, pokud vyhledávání členů vytvoří cokoli kromě skupiny metod nebo žádné shody. - K vyřešení přetížení použijte výslednou skupinu metod a prázdný seznam argumentů. Pokud výsledkem řešení přetížení nejsou žádné použitelné metody, výsledkem je nejednoznačnost nebo výsledkem je jedna nejlepší metoda, ale tato metoda je buď statická nebo není veřejná, zkontrolujte výčet rozhraní, jak je popsáno níže. Doporučuje se vydat upozornění, pokud rozlišení přetížení vytvoří cokoli kromě jednoznačné metody veřejné instance nebo žádné použitelné metody.
- Pokud návratový typ
Emetody «GetEnumerator» není třída, struktura nebo typ rozhraní, vygenerujte chybu a neprovádějte žádné další kroky. - Proveďte vyhledávání
Ečlenů s identifikátoremCurrenta bez argumentů typu. Pokud vyhledávání členů nevygeneruje žádnou shodu, výsledek je chyba nebo je výsledkem cokoli kromě vlastnosti veřejné instance, která umožňuje čtení, vyvolá chybu a neprovedou žádné další kroky. - Proveďte vyhledávání
Ečlenů s identifikátorem «MoveNext» a bez argumentů typu. Pokud vyhledávání členů nevygeneruje žádnou shodu, výsledkem je chyba nebo je výsledkem cokoli kromě skupiny metod, vygenerujte chybu a neprojděte žádné další kroky. - Proveďte rozlišení přetížení ve skupině metod s prázdným seznamem argumentů. Pokud má za následek rozlišení přetížení: žádné použitelné metody; nejednoznačnost; nebo jedinou nejlepší metodu, ale tato metoda je buď statická, nebo není veřejná, nebo její návratový typ není povolený návratový typ; pak vygenerujte chybu a neprojděte žádné další kroky.
- Typ kolekce je
X, enumerátor typ jeEa iterační typ je typCurrentvlastnosti.
- Proveďte vyhledávání členů u typu
- V opačném případě zkontrolujte výčet rozhraní:
- Pokud mezi všemi typy
Tᵢ, pro které existuje implicitní převod zX«IEnumerable»<Ti>, existuje jedinečný typTtakový, kterýTnenídynamica pro všechny ostatníTᵢexistuje implicitní převod z «IEnumerable»<T> na «IEnumerable»<Ti>, pak typ kolekce je rozhraní «IEnumerable»<T>, enumerátor typ je rozhraní «IEnumerator»<T>, a typ iterace jeT. - V opačném případě, pokud existuje více než jeden takový typ
T, pak vygenerujte chybu a neprojděte žádné další kroky.
- Pokud mezi všemi typy
Poznámka: Pokud má výraz hodnotu
null,System.NullReferenceExceptionje vyvolán za běhu. koncová poznámka
Implementace je povolena k implementaci daného foreach_statement odlišně; například z důvodů výkonu, pokud je chování v souladu s rozšířeními popsanými v §13.9.5.2 a §13.9.5.3.
13.9.5.2 Synchronní foreach
Synchronní foreach neobsahuje await klíčové slovo před klíčovým slovem foreach . Stanovení typu sběru, typ výčtu a typ iterace, jak je popsáno v §13.9.5.1, kde:
- «GetEnumerator» je
GetEnumeratormetoda. - «MoveNext» je
MoveNextmetoda s návratovým typembool. - «IEnumerable»<T> je
System.Collections.Generic.IEnumerable<T>rozhraní. - «IEnumerator»<T> je
System.Collections.Generic.IEnumerator<T>rozhraní.
Kromě toho jsou provedeny následující úpravy kroků v §13.9.5.1:
Před procesem popsaným v §13.9.5.1 jsou učiněny následující kroky:
- Je-li typ
Xvýrazu maticový typ, existuje implicitní převod odkazu zXIEnumerable<T>rozhraní, kdeTje typ prvku maticeX(§17.2.3). - Pokud je typ
Xvýrazu nadynamicrozhraní ().IEnumerableTyp kolekce jeIEnumerablerozhraní a typ enumerátoruIEnumeratorje rozhraní.varPokud je identifikátor uveden jako local_variable_type pak jedynamictyp iterace , jinak jeobject.
Pokud se proces v §13.9.5.1 dokončí bez vytvoření jednoho typu kolekce, typu enumerátoru a iteračního typu, je třeba provést následující kroky:
- Pokud existuje implicitní převod z
XSystem.Collections.IEnumerablerozhraní, pak typ kolekce je toto rozhraní, enumerátor typ je rozhraníSystem.Collections.IEnumeratora iterační typ jeobject. - V opačném případě se vytvoří chyba a neprovedou se žádné další kroky.
Příkaz foreach formuláře
foreach (V v in x) «embedded_statement»
se pak rovná:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
V v = (V)(T)e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
Proměnná e není viditelná ani přístupná pro výraz x nebo vložený příkaz nebo jakýkoli jiný zdrojový kód programu. Proměnná v je v vloženém příkazu jen pro čtení. Pokud neexistuje explicitní převod (§10.3) z T (typ iterace) na V ( local_variable_type v foreach prohlášení), dojde k chybě a neprovedou se žádné další kroky.
Je-li proměnná iterace referenční proměnnou (§9.7), prohlášení foreach formuláře
foreach (ref V v in x) «embedded_statement»
se pak rovná:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
ref V v = ref e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
Proměnná e není viditelná ani přístupná pro výraz x nebo vložený příkaz nebo jakýkoli jiný zdrojový kód programu. Referenční proměnná v je ve vloženém příkazu pro čtení i zápis, ale v nesmí být znovu přiřazena odkazem (§12.23.3). Pokud neexistuje převod identity (§10.2.2) z T (typu iterace) do V ( local_variable_type v foreach příkazu), dojde k chybě a neprovedou se žádné další kroky.
Příkaz foreach formuláře foreach (ref readonly V v in x) «embedded_statement» má podobný ekvivalentní formulář, ale referenční proměnná v je ref readonly ve vloženém příkazu, a proto nelze znovu přiřadit ani znovu přiřadit.
Umístění v uvnitř while smyčky je důležité pro to, jak je zachycen (§12.21.6.2) všemi anonymními funkcemi, ke kterým dochází v embedded_statement.
Příklad:
int[] values = { 7, 9, 13 }; Action f = null; foreach (var value in values) { if (f == null) { f = () => Console.WriteLine("First value: " + value); } } f();Pokud
vby byla v rozšířeném formuláři deklarována mimo smyčkuwhile, byla by sdílena mezi všemi iteracemi a její hodnota zaforsmyčkou by byla konečná hodnota, což je to,13co vyvolánífby se vytiskne. Místo toho, protože každá iterace má svou vlastní proměnnouv, ta zachycenáfv první iteraci bude i nadále obsahovat hodnotu7, což je to, co se vytiskne. (Všimněte si, že starší verze jazyka C# deklarovanévmimo smyčkuwhile.)konec příkladu
Tělo finally bloku je sestaveno podle následujících kroků:
Pokud existuje implicitní převod z
ESystem.IDisposablerozhraní, pakPokud
Eje nenulový typ hodnoty,finallyklauzule se rozbalí na sémantický ekvivalent:finally { ((System.IDisposable)e).Dispose(); }finallyV opačném případě se klauzule rozšíří na sémantický ekvivalent:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }s tím rozdílem, že pokud
Eje typ hodnoty nebo parametr typu, který vytvoří instanci na typ hodnoty, nesmí dojít k převoduenaSystem.IDisposablebox.
Jinak pokud
Eje zapečetěný typ,finallyklauzule se rozbalí na prázdný blok:finally {}finallyV opačném případě se klauzule rozbalí na:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }
Místní proměnná d není viditelná ani přístupná pro žádný uživatelský kód. Konkrétně není v konfliktu s žádnou jinou proměnnou, jejíž obor zahrnuje finally blok.
Pořadí, ve kterém foreach prochází prvky pole, je následující: U prvků jednorozměrných polí se prochází vzestupným pořadím indexu, počínaje indexem 0 a končí indexem Length – 1. U multidimenzionálních polí se prvky procházejí tak, aby se indexy pravé dimenze nejprve zvýšily, pak další levá dimenze atd.
Příklad: Následující příklad vypíše každou hodnotu v dvojrozměrném poli v pořadí prvků:
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(); } }Výstup vytvořený je následující:
1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9konec příkladu
Příklad: V následujícím příkladu
int[] numbers = { 1, 3, 5, 7, 9 }; foreach (var n in numbers) { Console.WriteLine(n); }typ
nje odvozenintbýt , iterační typnumbers.konec příkladu
13.9.5.3 await foreach (čekat na každý)
Asynchronní foreach používá await foreach syntaxi. Stanovení typu sběru, typ výčtu a typ iterace, jak je popsáno v §13.9.5.1, kde:
- «GetEnumerator» je
GetEnumeratorAsyncmetoda, která má očekávaný návratový typ (§12.9.9.2). - «MoveNext» je
MoveNextAsyncmetoda, která má očekávaný návratový typ (§12.9.9.2), kde je await_expression klasifikována jako (bool§12.9.9.3). - «IEnumerable»<T> je
System.Collections.Generic.IAsyncEnumerable<T>rozhraní. - «IEnumerator»<T> je
System.Collections.Generic.IAsyncEnumerator<T>rozhraní.
Jedná se o chybu typu iteraceawait foreach příkazu jako referenční proměnné (§9.7).
Výrok await foreach ve formátu
await foreach (T item in enumerable) «embedded_statement»
je sémanticky ekvivalentní:
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.
}
V případě, že výraz enumerable představuje výraz volání metody a jeden z parametrů je označen EnumeratorCancellationAttribute (§23.5.8) CancellationToken je předán metodě GetAsyncEnumerator . Jiné metody knihovny mohou vyžadovat CancellationToken předání GetAsyncEnumeratorsouboru . Pokud jsou tyto metody součástí výrazu enumerable, tokeny se zkombinují do jednoho tokenu, jako by CreateLinkedTokenSource a jeho Token vlastnost.
Tělo finally bloku je sestaveno podle následujících kroků:
Má-li přístupnou
Emetodu, je-liDisposeAsync()návratový typ očekáván (§12.9.9.2),finallyje klauzule rozšířena na sémantický ekvivalent:finally { await e.DisposeAsync(); }V opačném případě, pokud existuje implicitní převod z
ESystem.IAsyncDisposablerozhraní aEje nenulový typ hodnoty,finallyklauzule se rozšíří na sémantický ekvivalent:finally { await ((System.IAsyncDisposable)e).DisposeAsync(); }s tím rozdílem, že pokud
Eje typ hodnoty nebo parametr typu, který vytvoří instanci na typ hodnoty, nesmí dojít k převoduenaSystem.IAsyncDisposablebox.V opačném případě, pokud
Ejeref structtyp a má přístupnouDispose()metodu,finallyklauzule se rozšíří na sémantický ekvivalent:finally { e.Dispose(); }Jinak pokud
Eje zapečetěný typ,finallyklauzule se rozbalí na prázdný blok:finally {}finallyV opačném případě se klauzule rozbalí na:finally { System.IAsyncDisposable d = e as System.IAsyncDisposable; if (d != null) { await d.DisposeAsync(); } }
Místní proměnná d není viditelná ani přístupná pro žádný uživatelský kód. Konkrétně není v konfliktu s žádnou jinou proměnnou, jejíž obor zahrnuje finally blok.
Poznámka: Pokud
await foreachnení k dispozici asynchronní mechanismus dispose, nevyžaduje seesynchronně. koncová poznámka
13.10 Příkazy jump
13.10.1 Obecné
Příkazy skoku bezpodmínečně přenesou řízení.
jump_statement
: break_statement
| continue_statement
| goto_statement
| return_statement
| throw_statement
;
Umístění, do kterého příkaz jump přenáší řízení, se nazývá cíl příkazu jump.
Když se příkaz skoku vyskytuje v bloku a cíl tohoto příkazu skoku je mimo tento blok, příkaz jump se říká, že má opustit blok. I když příkaz jumpu může přenést řízení z bloku, nemůže nikdy převést řízení do bloku.
Provádění příkazů skoků je složité přítomností interveningových try příkazů. Pokud takové prohlášení chybí try , příkaz skoku bezpodmínečně přenese kontrolu z příkazu skoku do cíle. V přítomnosti takových intervening try příkazů je provádění složitější. Pokud příkaz jump ukončí jeden nebo více try bloků s přidruženými finally bloky, ovládací prvek se zpočátku přenese do finally bloku nejvnitřnějšího try příkazu. Když a pokud ovládací prvek dosáhne koncového finally bodu bloku, ovládací prvek se přenese do finally bloku dalšího ohraničujícího try příkazu. Tento proces se opakuje, dokud finally se nespustí bloky všech interveningových try příkazů.
Příklad: V následujícím kódu
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"); } }
finallybloky přidružené ke dvěmatrypříkazům se spustí před přenesením ovládacího prvku do cíle příkazu jump. Výstup vytvořený je následující:Before break Innermost finally block Outermost finally block After breakkonec příkladu
13.10.2 Příkaz break
Příkaz break ukončí nejbližší uzavření switch, while, do, for, nebo foreach příkaz.
break_statement
: 'break' ';'
;
Cílem break příkazu je koncový bod nejbližšího uzavření switch, while, do, , fornebo foreach příkazu.
break Pokud příkaz není uzavřen příkazem switch, , whiledo, for, nebo foreach příkaz, dojde k chybě kompilace čas.
Pokud je v sobě vnořeno více switch, while, do, fornebo foreach příkazy, break příkaz se vztahuje pouze na nejvnitřnější příkaz. Pro přenos kontroly mezi více úrovněmi goto vnoření se použije prohlášení (§13.10.4).
Prohlášení break nemůže ukončit finally blok (§13.11).
break Pokud dojde k příkazu v finally rámci bloku, cíl break příkazu musí být ve stejném finally bloku; jinak dojde k chybě v době kompilace.
Příkaz break se provede následujícím způsobem:
- Pokud příkaz
breakukončí jeden nebo vícetrybloků s přidruženýmifinallybloky, ovládací prvek se zpočátku přenese dofinallybloku nejvnitřnějšíhotrypříkazu. Když a pokud ovládací prvek dosáhne koncovéhofinallybodu bloku, ovládací prvek se přenese dofinallybloku dalšího ohraničujícíhotrypříkazu. Tento proces se opakuje, dokudfinallyse nespustí bloky všech interveningovýchtrypříkazů. - Ovládací prvek se přenese do cíle
breakpříkazu.
Vzhledem k tomu, že break prohlášení bezpodmínečně přenáší kontrolu jinde, koncový bod break příkazu není nikdy dostupný.
13.10.3 Příkaz continue
Příkaz continue spustí novou iteraci nejbližšího uzavření while, do, for, nebo foreach příkazu.
continue_statement
: 'continue' ';'
;
Cílem continue příkazu je koncový bod vloženého příkazu nejbližšího uzavřeného whilepříkazu , do, fornebo foreach příkazu.
continue Pokud příkaz není uzavřen příkazem while, do, fornebo foreach příkaz, dojde k chybě kompilace v době kompilace.
Pokud je v while sobě vnořeno více dopříkazů , for, foreachcontinue příkaz se vztahuje pouze na nejvnitřnější příkaz. Pro přenos kontroly mezi více úrovněmi goto vnoření se použije prohlášení (§13.10.4).
Prohlášení continue nemůže ukončit finally blok (§13.11).
continue Pokud dojde k příkazu v finally rámci bloku, cíl continue příkazu musí být ve stejném finally bloku; jinak dojde k chybě v době kompilace.
Příkaz continue se provede následujícím způsobem:
- Pokud příkaz
continueukončí jeden nebo vícetrybloků s přidruženýmifinallybloky, ovládací prvek se zpočátku přenese dofinallybloku nejvnitřnějšíhotrypříkazu. Když a pokud ovládací prvek dosáhne koncovéhofinallybodu bloku, ovládací prvek se přenese dofinallybloku dalšího ohraničujícíhotrypříkazu. Tento proces se opakuje, dokudfinallyse nespustí bloky všech interveningovýchtrypříkazů. - Ovládací prvek se přenese do cíle
continuepříkazu.
Vzhledem k tomu, že continue prohlášení bezpodmínečně přenáší kontrolu jinde, koncový bod continue příkazu není nikdy dostupný.
13.10.4 Příkaz goto
Příkaz goto přenese řízení na příkaz, který je označen popiskem.
goto_statement
: 'goto' identifier ';'
| 'goto' 'case' constant_expression ';'
| 'goto' 'default' ';'
;
Cílem gotopříkazu identifikátoru je označený příkaz s daným popiskem. Pokud popisek s daným názvem v aktuálním členu funkce neexistuje nebo pokud goto příkaz není v rozsahu popisku, dojde k chybě v době kompilace.
Poznámka: Toto pravidlo umožňuje použití
gotopříkazu k přenosu kontroly z vnořeného oboru, ale ne do vnořeného oboru. V příkladuclass 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}]"); } } }Příkaz
gotoslouží k přenosu kontroly z vnořeného oboru.koncová poznámka
Cílem goto case příkazu je seznam příkazů v bezprostředně uzavřeném switch příkazu (§13.8.3), který obsahuje case popisek s konstantním vzorem dané konstantní hodnoty a bez ochrany.
goto case Pokud příkaz není uzavřen příkazemswitch, pokud nejbližší uzavřený switch příkaz takový příkaz neobsahuje casenebo pokud constant_expression není implicitně konvertibilní (§10.2) na typ řízení nejbližšího uzavřeného switch příkazu, dojde k chybě v době kompilace.
Cílem goto default příkazu je seznam příkazů v bezprostředně uzavřeném switch prohlášení (§13.8.3), který obsahuje default popisek.
goto default Pokud příkaz není uzavřen příkazem switch nebo pokud nejbližší uzavřený switch příkaz neobsahuje default popisek, dojde k chybě kompilace.
Prohlášení goto nemůže ukončit finally blok (§13.11). Pokud dojde k goto příkazu v finally rámci bloku, cíl goto příkazu musí být ve stejném finally bloku nebo jinak dojde k chybě v době kompilace.
Příkaz goto se provede následujícím způsobem:
- Pokud příkaz
gotoukončí jeden nebo vícetrybloků s přidruženýmifinallybloky, ovládací prvek se zpočátku přenese dofinallybloku nejvnitřnějšíhotrypříkazu. Když a pokud ovládací prvek dosáhne koncovéhofinallybodu bloku, ovládací prvek se přenese dofinallybloku dalšího ohraničujícíhotrypříkazu. Tento proces se opakuje, dokudfinallyse nespustí bloky všech interveningovýchtrypříkazů. - Ovládací prvek se přenese do cíle
gotopříkazu.
Vzhledem k tomu, že goto prohlášení bezpodmínečně přenáší kontrolu jinde, koncový bod goto příkazu není nikdy dostupný.
13.10.5 Příkaz return
Příkaz return vrátí ovládací prvek aktuálnímu volajícímu člena funkce, ve kterém se zobrazí návratový příkaz, volitelně vrací hodnotu nebo variable_reference (§9.5).
return_statement
: 'return' ';'
| 'return' expression ';'
| 'return' 'ref' variable_reference ';'
;
Return_statement bez výrazu se nazývá return-no-value; jeden obsahující výraz se nazývá return-by-ref; a jeden obsahující refpouze výraz se nazývá return-by-value.
Jedná se o chybu v době kompilace, kdy se používá návratová hodnota z metody deklarované jako return-by-value nebo return-by-ref (§15.6.1).
Jedná se o chybu v době kompilace, která používá metodu return-by-ref deklarovanou jako return-no-value nebo return-by-value.
Jedná se o chybu v době kompilace, která používá návrat po hodnotě z metody deklarované jako return-no-value nebo return-by-ref.
Jedná se o chybu v době kompilace, pokud výraz není variable_reference nebo je odkazem na proměnnou, jejíž kontext ref-safe není kontext volajícího (§9.7.2).
Jedná se o chybu v době kompilace, která používá metodu return-by-ref z metody deklarované s method_modifierasync.
Člen funkce se říká, že vypočítá hodnotu , pokud se jedná o metodu vrácení po hodnotě (§15.6.11), přístupové objekty get vlastnosti nebo indexeru podle hodnoty nebo uživatelem definovaný operátor. Členy funkce, které jsou vráceny bez hodnoty, nevypočítají hodnotu a jsou metodami s efektivním návratovým typem void, nastavte přístupové objekty vlastností a indexerů, přidejte a odeberte přístupové objekty událostí, konstruktory instancí, statické konstruktory a finalizátory. Členy funkce, které jsou návratem po ref, nevypočítají hodnotu.
Pro návrat po hodnotě musí existovat implicitní převod (§10.2) z typu výrazu na účinný návratový typ (§15.6.11) obsahujícího člena funkce. Pro vrácení podle odkazu musí existovat převod identity (§10.2.2) mezi typem výrazu a účinným návratovým typem obsahujícího člena funkce.
return výrazy anonymních funkcí lze použít také v těle anonymních výrazů funkce (§12.21) a účastnit se určení, které převody existují pro tyto funkce (§10.7.1).
Jedná se o chybu v době kompilace, return která se zobrazí v finally bloku (§13.11).
Příkaz return se provede následujícím způsobem:
- Pro návrat po hodnotě se výraz vyhodnotí a jeho hodnota se převede na efektivní návratový typ obsahující funkce implicitním převodem. Výsledkem převodu se stane výsledná hodnota vytvořená funkcí. Pro návrat po ref se výraz vyhodnotí a výsledek se klasifikuje jako proměnná. Pokud argument return-by-ref ohraničující metody obsahuje
readonly, výsledná proměnná je jen pro čtení. -
returnPokud je příkaz uzavřený jedním nebo vícetrycatchbloky s přidruženýmifinallybloky, ovládací prvek se zpočátku přenese dofinallybloku nejvnitřnějšíhotrypříkazu. Když a pokud ovládací prvek dosáhne koncovéhofinallybodu bloku, ovládací prvek se přenese dofinallybloku dalšího ohraničujícíhotrypříkazu. Tento proces se opakuje, dokudfinallynebudou provedeny bloky všech uzavřenýchtrypříkazů. - Pokud obsahující funkce není asynchronní funkce, vrátí se ovládací prvek volajícímu obsahující funkce spolu s výslednou hodnotou( pokud existuje).
- Pokud je obsahující funkce asynchronní funkce, vrátí se ovládací prvek aktuálnímu volajícímu a výsledná hodnota, pokud existuje, je zaznamenána ve návratovém úkolu, jak je popsáno v (§15.14.3).
Vzhledem k tomu, že return prohlášení bezpodmínečně přenáší kontrolu jinde, koncový bod return příkazu není nikdy dostupný.
13.10.6 Příkaz throw
Příkaz throw vyvolá výjimku.
throw_statement
: 'throw' expression? ';'
;
Příkaz throw s výrazem vyvolá výjimku vytvořenou vyhodnocením výrazu. Výraz se implicitně převede na System.Exceptiona výsledek vyhodnocení výrazu se převede na System.Exception před vyvolání. Pokud je nullvýsledkem převodu , System.NullReferenceException je vyvolán místo toho.
Příkaz throw bez výrazu lze použít pouze v catch bloku, v takovém případě tento příkaz znovu vyvolá výjimku, která je aktuálně zpracována tímto catch blokem.
Vzhledem k tomu, že throw prohlášení bezpodmínečně přenáší kontrolu jinde, koncový bod throw příkazu není nikdy dostupný.
Při vyvolání výjimky se ovládací prvek přenese do první catch klauzule v uzavřeném try příkazu, který může zpracovat výjimku. Proces, který probíhá od okamžiku vyvolání výjimky do bodu přenosu ovládacího prvku do vhodné obslužné rutiny výjimky, se označuje jako šíření výjimek. Šíření výjimky se skládá z opakovaného vyhodnocení následujících kroků, dokud catch se nenajde klauzule, která odpovídá výjimce. V tomto popisu je bod vyvolání počátečním umístěním, ve kterém je vyvolán výjimka. Toto chování je uvedeno v §22.4.
V aktuálním členu funkce se prověří každý
trypříkaz, který uzavře bod vyvolání. Pro každý příkaz počínaje nejvnitřnějšímSpříkazemtrya končícím vnějšímtrypříkazem se vyhodnocují následující kroky:-
tryPokud blokSohraničuje bod vyvolání a pokudSmá jednu nebo vícecatchklauzulí,catchjsou klauzule zkoumány za účelem vyhledání vhodné obslužné rutiny pro výjimku. Prvnícatchklauzule, která určuje typTvýjimky (nebo parametr typu, který v době běhu označuje typTvýjimky), aby typEběhu odvozený zTje považován za shodu. Pokud klauzule obsahuje filtr výjimek, objekt výjimky je přiřazen k proměnné výjimky a filtr výjimky je vyhodnocen.catchPokud klauzule obsahuje filtr výjimek, je tato klauzule považována za shodu,catchpokud je filtr výjimky vyhodnocen jakotrue. Obecnácatchklauzule (§13.11) se považuje za shodu pro jakýkoli typ výjimky. Pokud se nachází odpovídajícícatchklauzule, šíření výjimky se dokončí přenesením ovládacího prvku do bloku tétocatchklauzule. - Jinak platí, že pokud
tryblok nebocatchblokSohraničuje bod vyvolání a pokudSobsahujefinallyblok, ovládací prvek se přenese dofinallybloku.finallyPokud blok vyvolá jinou výjimku, zpracování aktuální výjimky se ukončí. Jinak, pokud ovládací prvek dosáhne koncovéhofinallybodu bloku, zpracování aktuální výjimky bude pokračovat.
-
Pokud obslužná rutina výjimky nebyla umístěna v vyvolání aktuální funkce, vyvolání funkce je ukončeno a nastane jedna z následujících situací:
Pokud je aktuální funkce nesynchronní, výše uvedené kroky se opakují pro volajícího funkce s bodem vyvolání bodu throw odpovídajícímu příkazu, ze kterého byl vyvolán člen funkce.
Pokud je aktuální funkce asynchronní a vrací úlohu, zaznamená se v návratovém úkolu výjimka, která je vložena do chybného nebo zrušeného stavu, jak je popsáno v §15.14.3.
Pokud je aktuální funkce asyncuvní a vrací
void, je kontext synchronizace aktuálního vlákna oznámen, jak je popsáno v §15.14.4.
Pokud zpracování výjimek ukončí všechna vyvolání členů funkce v aktuálním vlákně, což znamená, že vlákno nemá pro výjimku žádnou obslužnou rutinu, vlákno se ukončí. Dopad takového ukončení je definován implementací.
13.11 Příkaz try
Tento try příkaz poskytuje mechanismus pro zachycení výjimek, ke kterým dochází během provádění bloku. Příkaz navíc try poskytuje možnost určit blok kódu, který se vždy spustí, když ovládací prvek opustí try příkaz.
try_statement
: 'try' block catch_clauses
| 'try' block catch_clauses? finally_clause
;
catch_clauses
: specific_catch_clause+
| specific_catch_clause* general_catch_clause
;
specific_catch_clause
: 'catch' exception_specifier exception_filter? block
| 'catch' exception_filter block
;
exception_specifier
: '(' type identifier? ')'
;
exception_filter
: 'when' '(' boolean_expression ')'
;
general_catch_clause
: 'catch' block
;
finally_clause
: 'finally' block
;
Try_statement se skládá z klíčového slovatry, za kterým následuje blok, pak nula nebo více catch_clauses a pak volitelná finally_clause. Musí existovat alespoň jeden catch_clause nebo finally_clause.
V exception_specifiertyp nebo jeho účinná základní třída, pokud se jedná o type_parameter, musí být System.Exception nebo typ, který je z něj odvozen.
catch Když klauzule určuje class_type i identifikátor, deklaruje se proměnná výjimky daného názvu a typu. Proměnná výjimky je zavedena do prostoru deklarace specific_catch_clause (§7.3). Během provádění exception_filter a catch bloku představuje proměnná výjimky aktuálně zpracovávanou výjimku. Pro účely kontroly určitého přiřazení je proměnná výjimky považována za rozhodně přiřazenou v celém rozsahu.
catch Pokud klauzule neobsahuje název proměnné výjimky, není možné získat přístup k objektu výjimky ve filtru a catch bloku.
Klauzule catch , která specifikuje žádný typ výjimky ani název proměnné výjimky, se nazývá obecná catch klauzule. Příkaz try může mít pouze jednu obecnou catch klauzuli, a pokud existuje, bude to poslední catch klauzule.
Poznámka: Některé programovací jazyky mohou podporovat výjimky, které nejsou reprezentovatelné jako objekt odvozený z
System.Exception, ačkoli tyto výjimky nelze nikdy generovat kódem jazyka C#. K zachycení těchto výjimek lze použít obecnoucatchklauzuli. Obecná klauzule je tedy sémanticky odlišná od té, která určuje typcatch, v tom,System.Exceptionže první klauzule může také zachytit výjimky z jiných jazyků. koncová poznámka
Aby bylo možné vyhledat obslužnou rutinu výjimky, catch jsou klauzule zkoumány v lexikálním pořadí.
catch Pokud klauzule určuje typ, ale bez filtru výjimek, jedná se o chybu v době kompilace pro pozdější catch klauzuli stejného try příkazu, která určuje typ, který je stejný jako nebo je odvozen z tohoto typu.
Poznámka: Bez tohoto omezení by bylo možné zapisovat nedostupné
catchklauzule. koncová poznámka
catch V rámci bloku throw nelze použít příkaz (§13.10.6) bez výrazu k opětovnému vyvolání výjimky zachycené blokemcatch. Přiřazení proměnné výjimky nemění výjimku, která se znovu vyvolá.
Příklad: V následujícím kódu
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
Fzachytí výjimku, zapíše do konzoly některé diagnostické informace, změní proměnnou výjimky a znovu vyvolá výjimku. Výjimkou, která se znovu vyvolá, je původní výjimka, takže výstup se vytvoří takto:Exception in F: G Exception in Main: GPokud by první
catchblok vyvolalemísto opětovného načítání aktuální výjimky, výstup vytvořený by byl následující:Exception in F: G Exception in Main: Fkonec příkladu
Jedná se o chybu v době kompilace , breakcontinuenebo goto příkaz pro přenos řízení z finally bloku. Pokud se v breakbloku vyskytuje continue příkaz , gotonebo finally příkaz, cíl příkazu musí být ve stejném finally bloku nebo jinak dojde k chybě v době kompilace.
Jedná se o chybu v době kompilace, return ke které má příkaz dojít v finally bloku.
Když provádění dosáhne try příkazu, ovládací prvek se přenese do try bloku. Pokud ovládací prvek dosáhne koncového try bodu bloku bez šíření výjimky, ovládací prvek se přenese do finally bloku, pokud existuje. Pokud žádný blok neexistuje finally , ovládací prvek se přenese do koncového try bodu příkazu.
Pokud byla výjimka rozšířena, catch jsou klauzule ( pokud existují) zkoumány v lexikálním pořadí, kde se hledá první shoda pro výjimku. Hledání odpovídající catch klauzule pokračuje se všemi uzavřenými bloky, jak je popsáno v §13.10.6. Klauzule catch je shoda, pokud typ výjimky odpovídá libovolnému exception_specifier a jakýkoli exception_filter je pravdivý. Klauzule catch bez exception_specifier odpovídá jakémukoli typu výjimky. Typ výjimky odpovídá exception_specifier , pokud exception_specifier určuje typ výjimky nebo základní typ výjimky. Pokud klauzule obsahuje filtr výjimek, objekt výjimky je přiřazen k proměnné výjimky a filtr výjimky je vyhodnocen. Pokud vyhodnocení boolean_expression pro exception_filter vyvolá výjimku, tato výjimka se zachytí a filtr výjimky se vyhodnotí jako false.
Pokud byla rozšířena výjimka a byla nalezena odpovídající catch klauzule, ovládací prvek se přenese do prvního odpovídajícího catch bloku. Pokud ovládací prvek dosáhne koncového catch bodu bloku bez šíření výjimky, ovládací prvek se přenese do finally bloku, pokud existuje. Pokud žádný blok neexistuje finally , ovládací prvek se přenese do koncového try bodu příkazu. Pokud byla výjimka rozšířena z catch bloku, řízení se přenese do finally bloku, pokud existuje. Výjimka se rozšíří do dalšího ohraničujícího try příkazu.
Pokud byla výjimka rozšířena a nebyla nalezena žádná odpovídající catch klauzule, řízení se přenese do finally bloku, pokud existuje. Výjimka se rozšíří do dalšího ohraničujícího try příkazu.
Příkazy finally bloku se vždy spustí, když ovládací prvek opustí try příkaz. To platí, zda se přenos ovládacích prvků vyskytuje v důsledku normálního spuštění, v důsledku provádění breakpříkazu , continue, gotonebo return příkazu, nebo v důsledku šíření výjimky z try příkazu. Pokud ovládací prvek dosáhne koncového finally bodu bloku bez rozšíření výjimky, ovládací prvek se přenese na koncový bod try příkazu.
Pokud je při provádění finally bloku vyvolána výjimka a není zachycena ve stejném finally bloku, výjimka se rozšíří do dalšího ohraničujícího try příkazu. Pokud v procesu šíření došlo k jiné výjimce, dojde ke ztrátě této výjimky. Postup šíření výjimky je podrobněji popsán v popisu throw prohlášení (§13.10.6).
Příklad: V následujícím kódu
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
Methodvyvolá výjimku. První akcí je prozkoumat ohraničujícícatchklauzule a provádět všechny filtry výjimek. Pak sefinallyklauzule vyřídíMethodpřed přenosem řízení do nadřazené odpovídajícícatchklauzule. Výsledný výstup je:Filter Finally Catchkonec příkladu
Blok trytry příkazu je dostupný, pokud try je příkaz dostupný.
Blok catchtry příkazu je dostupný, pokud try je příkaz dostupný.
Blok finallytry příkazu je dostupný, pokud try je příkaz dostupný.
Koncový bod try příkazu je dostupný, pokud jsou splněny obě následující podmínky:
- Koncový bod
trybloku je dosažitelný nebo je dostupný koncový bod alespoň jednohocatchbloku. -
finallyPokud je blok k dispozici, je koncový bodfinallybloku dostupný.
13.12 Zaškrtnuté a nezaškrtnuté příkazy
Příkazy checked slouží k řízení unchecked kontroly přetečení pro aritmetické operace a převody celočíselného typu.
checked_statement
: 'checked' block
;
unchecked_statement
: 'unchecked' block
;
Příkaz checked způsobí, že se všechny výrazy v bloku vyhodnotí v kontrolovaném kontextu a unchecked příkaz způsobí, že se všechny výrazy v bloku vyhodnotí v nezaškrtnutém kontextu.
Tyto checked příkazy unchecked jsou přesně ekvivalentní checked operátorům a unchecked operátorům (§12.8.20), s tím rozdílem, že pracují s bloky namísto výrazů.
13.13 Příkaz lock
Příkaz lock získá zámek vzájemného vyloučení pro daný objekt, spustí příkaz a pak uvolní zámek.
lock_statement
: 'lock' '(' expression ')' embedded_statement
;
Výraz lock příkazu označuje hodnotu typu známého jako odkaz. Pro výraz příkazu se nikdy neprovádí implicitní převod boxingu (lock), a proto se jedná o chybu v době kompilace, která výrazu označuje hodnotu value_type.
Příkaz lock formuláře
lock (x) ...
kde x je výraz reference_type, je přesně ekvivalentní:
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(x, ref __lockWasTaken);
...
}
finally
{
if (__lockWasTaken)
{
System.Threading.Monitor.Exit(x);
}
}
s tím rozdílem, že x se vyhodnotí pouze jednou.
I když je uzamčeno vzájemné vyloučení, kód spuštěný ve stejném spouštěcím vlákně může také získat a uvolnit zámek. Kód spuštěný v jiných vláknech je však zablokovaný v získání zámku, dokud se zámek nevolní.
13.14 Příkaz using
13.14.1 Obecné
Příkaz using získá jeden nebo více prostředků, spustí příkaz a pak odstraní prostředek.
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 prostředku je buď třída, nebo struktura bez odkazu, která implementuje buď nebo obě System.IDisposable rozhraní nebo System.IAsyncDisposable rozhraní, která zahrnuje jednu metodu bez parametrů s názvem Dispose a/nebo DisposeAsync; nebo ref strukturu, která obsahuje metodu s názvem Dispose se stejným podpisem, jaký deklaroval System.IDisposable. Kód, který používá prostředek, může volat Dispose nebo DisposeAsync indikovat, že prostředek už není potřeba.
Pokud je forma resource_acquisitionlocal_variable_declaration , typ local_variable_declaration musí být dynamic buď typ prostředku. Pokud je forma resource_acquisitionvýrazem , bude mít tento výraz typ prostředku. Pokud await je k dispozici, typ prostředku se implementuje System.IAsyncDisposable. Typ ref struct nemůže být typem using prostředku pro příkaz s modifikátorem await .
Místní proměnné deklarované v resource_acquisition jsou jen pro čtení a musí obsahovat inicializátor. K chybě v době kompilace dochází v případě, že se vložený příkaz pokusí upravit tyto místní proměnné (prostřednictvím přiřazení nebo ++ operátorů -- ), převezměte jejich adresu nebo je předejte jako odkaz nebo výstupní parametry.
Příkaz using se překládá do tří částí: získání, využití a vyřazení. Použití prostředku je implicitně uzavřeno v try příkazu, který obsahuje finally klauzuli. Tato finally klauzule odstraní prostředek. Pokud se výraz získání vyhodnotí jako null, pak se neprovedou žádné volání Dispose (nebo DisposeAsync) a nevyvolá se žádná výjimka. Pokud je zdroj typu, dynamic je dynamicky převeden prostřednictvím implicitního dynamického převodu (§10.2.10) na IDisposable (nebo IAsyncDisposable) během získávání, aby se zajistilo, že převod bude úspěšný před použitím a odstraněním.
Příkaz using formuláře
using (ResourceType resource = «expression» ) «statement»
odpovídá jedné ze tří možných formulací. Pokud jde o typ hodnoty typu null nebo parametr typu s omezením typu hodnota (ResourceType), je formulace pro prostředky bez hodnoty souměrně ekvivalentní
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
((IDisposable)resource).Dispose();
}
}
s tím rozdílem, že přetypování resourceSystem.IDisposable nesmí způsobit výskyt boxů.
Jinak, pokud ResourceType je dynamic, formulace je
{
ResourceType resource = «expression»;
IDisposable d = resource;
try
{
«statement»;
}
finally
{
if (d != null)
{
d.Dispose();
}
}
}
Jinak je formulace
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IDisposable d = (IDisposable)resource;
if (d != null)
{
d.Dispose();
}
}
}
Pro prostředky ref struktury je jedinou séanticky ekvivalentní formulace
{
«ResourceType» resource = «expression»;
try
{
«statement»;
}
finally
{
resource.Dispose();
}
}
V jakékoli formulaci resource je proměnná v vloženém příkazu jen pro čtení a d proměnná je nepřístupná a neviditelná pro vložený příkaz.
Příkaz using formuláře:
using («expression») «statement»
má stejné možné formulace.
Když resource_acquisition má formu local_variable_declaration, je možné získat více prostředků daného typu. Příkaz using formuláře
using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) «statement»
je přesně ekvivalentní sekvenci vnořených using příkazů:
using (ResourceType r1 = e1)
using (ResourceType r2 = e2)
...
using (ResourceType rN = eN)
«statement»
Příklad: Následující příklad vytvoří soubor s názvem log.txt a zapíše do souboru dva řádky textu. Příklad pak otevře stejný soubor pro čtení a zkopíruje obsažené řádky textu do konzoly.
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); } } } }Vzhledem k tomu, že
TextWriteraTextReadertřídy implementujíIDisposablerozhraní, může příklad použítusingpříkazy k zajištění, že podkladový soubor je správně uzavřen po operacích zápisu nebo čtení.konec příkladu
Je-li ResourceType typ odkazu, který implementuje IAsyncDisposable. Další formulace pro await using provádění podobných náhrad ze synchronní Dispose metody na asynchronní DisposeAsync metodu. Výrok await using ve formátu
await using (ResourceType resource = «expression» ) «statement»
je sémanticky ekvivalentní formulací zobrazeným níže namísto IAsyncDisposableIDisposable, DisposeAsync místo Disposea Task vrácené z DisposeAsync je await:
await using (ResourceType resource = «expression» ) «statement»
je sémanticky ekvivalentní
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IAsyncDisposable d = (IAsyncDisposable)resource;
if (d != null)
{
await d.DisposeAsync();
}
}
}
Poznámka: Všechny jump statements (§13.10) v embedded_statement musí odpovídat rozšířené formě
usingprohlášení. koncová poznámka
13.14.2 Použití deklarace
Syntaktická varianta příkazu using je deklarace using.
using_declaration
: 'await'? 'using' non_ref_local_variable_declaration ';' statement_list?
;
Deklarace using má stejnou sémantiku jako a lze ji přepsat jako odpovídající formu pořízení zdroje příkazu using (§13.14.1), a to následujícím způsobem:
using «local_variable_type» «local_variable_declarators»
// statements
je sémanticky ekvivalentní
using («local_variable_type» «local_variable_declarators»)
{
// statements
}
a
await using «local_variable_type» «local_variable_declarators»
// statements
je sémanticky ekvivalentní
await using («local_variable_type» «local_variable_declarators»)
{
// statements
}
Životnost proměnných deklarovaných v non_ref_local_variable_declaration se rozšiřuje na konec oboru, ve kterém jsou deklarovány. Tyto proměnné se pak odstraní v obráceném pořadí, ve kterém jsou deklarovány.
static void M()
{
using FileStream f1 = new FileStream(...);
using FileStream f2 = new FileStream(...), f3 = new FileStream(...);
...
// Dispose f3
// Dispose f2
// Dispose f1
}
Prohlášení o použití se nesmí objevit přímo uvnitř switch_label, ale může být v bloku uvnitř switch_label.
13.15 Příkaz výnosu
Příkaz yield se používá v bloku iterátoru (§13.3) k vyjádření hodnoty objektu enumerátoru (§15.15.5) nebo výčtového objektu (§15.15.6) iterátoru nebo k označení konce iterace.
yield_statement
: 'yield' 'return' expression ';'
| 'yield' 'break' ';'
;
yieldje kontextové klíčové slovo (§6.4.4) a má zvláštní význam pouze v případech, kdy se používá bezprostředně před nebo return klíčovým slovembreak.
Existuje několik omezení, kdy yield se příkaz může zobrazit, jak je popsáno v následujícím příkladu.
- Jedná se o chybu v době kompilace, která se zobrazí
yieldmimo method_body, operator_body nebo accessor_body. - Jedná se o chybu v době kompilace pro
yieldpříkaz (z obou formulářů), který se zobrazí uvnitř anonymní funkce. - Jedná se o chybu v době kompilace pro
yieldpříkaz (z obou formulářů), který se zobrazí vfinallyklauzulitrypříkazu. - Jedná se o chybu v době kompilace,
yield returnkdy se příkaz zobrazí kdekoli vtrypříkazu, který obsahuje všechny catch_clauses.
Příklad: Následující příklad ukazuje některé platné a neplatné použití
yieldpříkazů.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 }konec příkladu
Implicitní převod (§10.2) musí existovat z typu výrazu ve yield return výrazu na typ výnosu (§15.15.4) iterátoru.
Příkaz yield return se provede následujícím způsobem:
- Výraz zadaný v příkazu je vyhodnocen, implicitně převeden na typ výnosu a přiřazen k
Currentvlastnosti enumerator objektu. - Spuštění bloku iterátoru je pozastaveno.
yield returnPokud je příkaz v jednom nebo vícetryblocích, přidruženéfinallybloky se v tuto chvíli nespustí. - Metoda
MoveNextobjektu enumerátoru se vrátítruedo svého volajícího, což označuje, že objekt enumerátoru byl úspěšně rozšířen na další položku.
Další volání metody enumerátoru objektu MoveNext obnoví provádění bloku iterátoru od místa, kde byl naposledy pozastaven.
Příkaz yield break se provede následujícím způsobem:
-
yield breakPokud je příkaz uzavřený jedním nebo vícetrybloky s přidruženýmifinallybloky, ovládací prvek je zpočátku přenesen dofinallybloku nejvnitřnějšíhotrypříkazu. Když a pokud ovládací prvek dosáhne koncovéhofinallybodu bloku, ovládací prvek se přenese dofinallybloku dalšího ohraničujícíhotrypříkazu. Tento proces se opakuje, dokudfinallynebudou provedeny bloky všech uzavřenýchtrypříkazů. - Ovládací prvek se vrátí volajícímu bloku iterátoru. Jedná se o metodu
MoveNextneboDisposemetodu objektu enumerátoru.
Vzhledem k tomu, že yield break prohlášení bezpodmínečně přenáší kontrolu jinde, koncový bod yield break příkazu není nikdy dostupný.
ECMA C# draft specification