Sdílet prostřednictvím


12 Výrazy

12.1 Obecné

Výraz je posloupnost operátorů a operandů. Tato klauzule definuje syntaxi, pořadí vyhodnocení operandů a operátorů a význam výrazů.

12.2 Klasifikace výrazů

12.2.1 Obecné

Výsledek výrazu je klasifikován jako jeden z následujících:

  • Hodnota. Každá hodnota má přidružený typ.
  • Proměnná. Není-li zadáno jinak, je proměnná explicitně zadána a má přidružený typ, konkrétně deklarovaný typ proměnné. Implicitně typovaná proměnná nemá přidružený typ.
  • Literál null. Výraz s touto klasifikací lze implicitně převést na referenční typ nebo nulovatelný typ hodnoty.
  • Anonymní funkce. Výraz s touto klasifikací lze implicitně převést na kompatibilní typ delegáta nebo typ stromu výrazu.
  • Řazená kolekce členů. Každá řazená kolekce členů má pevný počet prvků, každý s výrazem a volitelným názvem elementu řazené kolekce členů.
  • Přístup k vlastnosti. Každý přístup k vlastnostem má přidružený typ, konkrétně typ vlastnosti. Navíc může mít přístup k vlastnostem přidružený výraz instance. Při vyvolání přístupového objektu vlastnosti instance se výsledkem vyhodnocení výrazu instance stane instance reprezentovaná this (§12.8.14).
  • Přístup k indexeru. Každý přístup indexeru má přidružený typ, konkrétně typ prvku indexeru. Přístup indexeru má navíc přidružený výraz instance a přidružený seznam argumentů. Při vyvolání přístupového objektu indexeru se výsledek vyhodnocení výrazu instance stane instancí reprezentovanou this (§12.8.14) a výsledkem vyhodnocení seznamu argumentů se stane seznam parametrů vyvolání.
  • Nic. K tomu dochází, když výraz je vyvolání metody s návratovým typem void. Výraz klasifikovaný jako nic je platný pouze v kontextu statement_expression (§13.7) nebo jako tělo lambda_expression (§12.19).

U výrazů, které se vyskytují jako dílčí výrazy větších výrazů, s označenými omezeními lze výsledek také klasifikovat jako jeden z následujících výrazů:

  • Jmenný prostor. Výraz s touto klasifikací se může objevit pouze na levé straně member_access (§12.8.7). V jakémkoli jiném kontextu výraz klasifikovaný jako obor názvů způsobí chybu v době kompilace.
  • Jeden typ. Výraz s touto klasifikací se může objevit pouze na levé straně member_access (§12.8.7). V jakémkoli jiném kontextu výraz klasifikovaný jako typ způsobí chybu v době kompilace.
  • Skupina metod, což je sada přetížených metod vyplývajících z vyhledávání členů (§12.5). Skupina metod může mít přidružený výraz instance a seznam argumentů přidruženého typu. Při vyvolání metody instance se výsledkem vyhodnocení výrazu instance stane instance reprezentovaná this (§12.8.14). Skupina metod je povolena v invocation_expression (§12.8.10) nebo delegate_creation_expression (§12.8.17.6) a lze jej implicitně převést na kompatibilní typ delegáta (§10.8). V jakémkoli jiném kontextu výraz klasifikovaný jako skupina metod způsobí chybu v době kompilace.
  • Přístup k události. Každý přístup k události má přidružený typ, konkrétně typ události. Přístup k události může mít navíc přidružený výraz instance. Přístup k události se může zobrazit jako levý operand operátorů += a -= (§12.21.5). V jakémkoli jiném kontextu výraz klasifikovaný jako přístup k události způsobí chybu v době kompilace. Při vyvolání přístupového objektu události instance se výsledkem vyhodnocení výrazu instance stane instance reprezentovaná this (§12.8.14).
  • Výraz throw, který lze použít v několika kontextech k vyvolání výjimky. Výraz throw může být převeden implicitním převodem na libovolný typ.

Přístup k vlastnosti nebo přístup k indexeru je vždy přetříděn jako hodnota provedením vyvolání přístupového objektu get nebo objektu set. Konkrétní přístup je určen kontextem vlastnosti nebo přístupu indexeru: Pokud je přístup cílem přiřazení, je vyvolán přístupový objekt sady pro přiřazení nové hodnoty (§12.21.2). V opačném případě je vyvolán get accessor k získání aktuální hodnoty (§12.2.2).

Přístupový objekt instance je přístup k vlastnostem v instanci, přístup k události v instanci nebo přístup k indexeru.

12.2.2 Hodnoty výrazů

Většina konstrukcí, které zahrnují výraz, nakonec vyžaduje, aby výraz označoval hodnotu. Pokud skutečný výraz v takových případech označuje obor názvů, typ, skupinu metod nebo nic, dojde k chybě v době kompilace. Pokud však výraz označuje přístup k vlastnosti, přístup indexeru nebo proměnnou, hodnota vlastnosti, indexeru nebo proměnné je implicitně nahrazena:

  • Hodnota proměnné je jednoduše hodnota aktuálně uložená v umístění úložiště identifikované proměnnou. Proměnná se považuje za rozhodně přiřazenou (§9,4) před získáním jeho hodnoty nebo jinak dojde k chybě v době kompilace.
  • Hodnota přístupového výrazu vlastnosti se získá vyvoláním přístupového objektu get vlastnosti. Pokud vlastnost nemá přístup k objektu get, dojde k chybě v době kompilace. V opačném případě se provede vyvolání člena funkce (§12.6.6) a výsledkem vyvolání se stane hodnota výrazu přístupu k vlastnosti.
  • Hodnota přístupového výrazu indexeru se získá vyvoláním přístupového objektu get indexeru. Pokud indexer nemá žádné přístupové objekty get, dojde k chybě v době kompilace. Jinak se provede volání člena funkce (§12.6.6) s argumentovým seznamem přidruženým k výrazu přístupu indexeru a výsledkem tohoto volání se stane hodnota výrazu přístupu indexeru.
  • Hodnota výrazu řazené kolekce členů je získána použitím implicitního převodu řazené kolekce členů (§10.2.13) na typ výrazu řazené kolekce členů. Jedná se o chybu při získání hodnoty výrazu řazené kolekce členů, který nemá typ.

12.3 Statické a dynamické vazby

12.3.1 Obecné

Vazby je proces určení toho, na co operace odkazuje, na základě typu nebo hodnoty výrazů (argumenty, operandy, příjemce). Například vazba volání metody je určena na základě typu objektu a argumentů. Operátorová vazba je určena na základě typu jeho operandů.

V jazyce C# je vazba operace obvykle určena v době kompilace na základě typu kompilace jeho dílčích výrazů. Podobně platí, že pokud výraz obsahuje chybu, je zjištěna a hlášena v době kompilace. Tento přístup se označuje jako statické vazby.

Pokud je však výraz dynamickým výrazem (tj. má typ dynamic), znamená to, že všechny vazby, které se účastní, by měly být založené na jeho typu za běhu, a ne na typu, který má v době kompilace. Vazba takové operace je proto odložena do doby, kdy se má operace spustit během spuštění programu. To se označuje jako dynamické vazby.

Pokud je operace dynamicky svázaná, provádí se v době kompilace malá nebo žádná kontrola. Místo toho v případě selhání vazby za běhu se chyby oznamují jako výjimky.

Na vazby se vztahují následující operace v jazyce C#:

  • Přístup ke členům: e.M
  • Vyvolání metody: e.M(e₁,...,eᵥ)
  • Vyvolání delegáta: e(e₁,...,eᵥ)
  • Přístup k elementům: e[e₁,...,eᵥ]
  • Vytváření objektů: nové C(e₁,...,eᵥ)
  • Přetížené unární operátory: +, -, ! (pouze logická negace), ~, ++, --, true, false
  • Přetížené binární operátory: +, -, *, /, %, &, &&, |, ||, ??, ^, <<, >>, ==, !=, >, <, >=, <=
  • Operátory přiřazení: =, = ref, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=
  • Implicitní a explicitní převody

Pokud nejsou zahrnuty žádné dynamické výrazy, jazyk C# ve výchozím nastavení použije statickou vazbu, což znamená, že v procesu výběru se použijí typy dílčích výrazů za doby kompilace. Pokud je však jedním z dílčích výrazů v operacích uvedených výše dynamický výraz, operace je místo toho dynamicky vázána.

Jedná se o chybu v době kompilace, pokud je vyvolání metody dynamicky vázané a všechny parametry, včetně příjemce, jsou vstupní parametry.

12.3.2 Doba vazby

Statické vazby probíhají v době kompilace, zatímco dynamické vazby probíhají za běhu. V následujících dílčích návazcích se termín doba vazby odkazuje buď na dobu kompilace, nebo na dobu běhu v závislosti na tom, kdy se vazba provádí.

příklad: Následující příklad znázorňuje pojmy statické a dynamické vazby a doby vazby:

object o = 5;
dynamic d = 5;
Console.WriteLine(5); // static binding to Console.WriteLine(int)
Console.WriteLine(o); // static binding to Console.WriteLine(object)
Console.WriteLine(d); // dynamic binding to Console.WriteLine(int)

První dvě volání jsou staticky svázaná: přetížení Console.WriteLine je vybráno na základě kompilačního času jejich argumentů. Proto je doba vazby kompilace .

Třetí volání je dynamicky vázáno: přetížení Console.WriteLine je vybráno na základě běhového typu jeho argumentu. K tomu dochází, protože argument je dynamický výraz – jeho typ kompilace je dynamický. Čas vazby třetího volání je tedy za běhu .

koncového příkladu

12.3.3 Dynamické vazby

Tato dílčí položka je informativní.

Dynamická vazba umožňuje programům jazyka C# pracovat s dynamickými objekty, tj. objekty, které nedodržují normální pravidla systému typů jazyka C#. Dynamické objekty můžou být objekty z jiných programovacích jazyků s různými typy systémů nebo se mohou jednat o objekty, které jsou programově nastaveny tak, aby implementovaly vlastní sémantiku vazeb pro různé operace.

Mechanismus, kterým dynamický objekt implementuje vlastní sémantiku, je definován implementací. Dané rozhraní – znovu definované implementací – je implementováno dynamickými objekty, které signalizují prostředí runtime jazyka C#, že mají speciální sémantiku. Kdykoli probíhá operace na dynamickém objektu, převezme jejich vlastní sémantika vazby, nikoli ta jazyka C#, místo těch specifikovaných v této specifikaci pro C#.

Zatímco účelem dynamické vazby je umožnit spolupráci s dynamickými objekty, jazyk C# umožňuje dynamické vazby u všech objektů, ať už jsou dynamické, nebo ne. To umožňuje plynulejší integraci dynamických objektů, protože výsledky operací na nich nemusí být samy o sobě dynamickými objekty, ale jsou stále typem neznámým pro programátora v době kompilace. Dynamické vazby mohou také pomoci eliminovat kód založený na reflexi, i když nejsou zapojeny žádné dynamické objekty.

12.3.4 Typy dílčích výrazů

Pokud je operace staticky svázaná, typ dílčího výrazu (např. příjemce a argument, index nebo operand) se vždy považuje za typ kompilace daného výrazu.

Pokud je operace dynamicky vázána, typ dílčího výrazu je určen různými způsoby v závislosti na typu kompilace dílčího výrazu:

  • Dílčí výraz dynamického typu v čase kompilace se považuje za typ hodnoty, ke které se výraz vyhodnotí během běhu programu.
  • Dílčí výraz, jehož typ během kompilace je parametr typu, se považuje za typ, ke kterému je parametr typu vázán při běhu programu.
  • V opačném případě se podvýraz považuje za typ určený během kompilace.

12.4 Operátory

12.4.1 Obecné

Výrazy se vytvářejí z operandů a operátorů. Operátory výrazu označují, které operace se mají použít na operandy.

Příklad: Příklady operátorů zahrnují +, -, *, /a new. Mezi příklady operandů patří literály, pole, místní proměnné a výrazy. konec příkladu

Existují tři druhy operátorů:

  • Unární operátory. Unární operátory přebírají jeden operand a používají buď prefixovou notaci (například –x), nebo postfixovou notaci (například x++).
  • Binární operátory. Binární operátory mají dva operandy a všechny používají infixační notaci (například x + y).
  • Ternární operátor. Existuje pouze jeden ternární operátor, ?:; přebírá tři operandy a používá infixační notaci (c ? x : y).

Pořadí vyhodnocování operátorů ve výrazu je určeno prioritou a asociativitou operátorů (§12.4.2).

Operandy ve výrazu se vyhodnocují zleva doprava.

Příklad: V F(i) + G(i++) * H(i)se volá metoda F pomocí staré hodnoty i, metoda G je volána se starou hodnotou ia nakonec metoda H je volána s novou hodnotou i. Toto je oddělené od a nesouvisí s pořadím operátoru. konec příkladu

Některé operátory mohou být přetížené. Přetížení operátoru (§12.4.3) umožňuje určit implementace operátoru definované uživatelem pro operace, kde jeden nebo oba operandy jsou uživatelsky definované třídy nebo typu struktury.

12.4.2 Přednost operátorů a asociativita

Pokud výraz obsahuje více operátorů, prioritu operátorů určuje pořadí, ve kterém jsou jednotlivé operátory vyhodnoceny.

Poznámka: Například výraz x + y * z se vyhodnotí jako x + (y * z), protože operátor * má vyšší prioritu než binární operátor +. koncová poznámka

Priorita operátoru je stanovena definicí přidružené gramatické produkce.

Poznámka: Například výraz aditivní se skládá z posloupnosti výrazů multiplikativníoddělených operátory + nebo -, takže operátory + a - mají nižší prioritu než operátory *, /a %. koncová poznámka

Poznámka: Následující tabulka shrnuje všechny operátory v pořadí od nejvyššího po nejnižší:

podčlánek kategorie operátory
§12.8 Primární x.y x?.y f(x) a[x] a?[x] x++ x-- x! new typeof default checked unchecked delegate stackalloc
§12.9 Unární + - !x ~ ++x --x (T)x await x
§12.10 Multiplikativní * / %
§12.10 Aditivní + -
§12.11 Směna << >>
§12.12 Relační a typové testování < > <= >= is as
§12.12 Rovnost == !=
§12.13 Logická operace AND &
§12.13 Logický XOR ^
§12.13 Logické NEBO \|
§12.14 Podmíněný operátor AND &&
§12.14 Podmíněné OR \|\|
§12.15 a §12.16 Spojení pro null a výraz throw ?? throw x
§12.18 Podmíněný ?:
§12.21 a §12.19 Výraz přiřazení a lambda = = ref *= /= %= += -= <<= >>= &= ^= \|= =>

koncová poznámka

Pokud dojde k operandu mezi dvěma operátory se stejnou prioritou, asociativita operátorů řídí pořadí provádění operací:

  • Kromě operátorů přiřazení a operátoru nulového sjednocení jsou všechny binární operátory asociativnívlevo, což znamená, že operace se provádějí zleva doprava.

    Příklad: x + y + z se vyhodnotí jako (x + y) + z. konec příkladu

  • Operátory přiřazení, operátor nulového sjednocení a podmíněný operátor (?:) jsou asociativní, což znamená, že operace se provádějí zprava doleva.

    Příklad: x = y = z se vyhodnotí jako x = (y = z). koncového příkladu

Prioritu a asociativitu lze řídit pomocí závorek.

Příklad: x + y * z nejprve vynásobí yz a potom přidá výsledek k x, ale (x + y) * z nejprve přidá x a y a výsledek pak vynásobí z. konec příkladu

12.4.3 Přetížení operátoru

Všechny unární a binární operátory mají předdefinované implementace. Kromě toho lze uživatelem definované implementace zavést zahrnutím deklarací operátorů (§15.10) ve třídách a strukturách. Implementace operátoru definované uživatelem mají vždy přednost před předdefinovanými implementacemi operátorů: Pouze pokud neexistují implementace předdefinovaných operátorů definované uživatelem, budou se považovat za předdefinované implementace operátoru, jak je popsáno v §12.4.4 a §12.4.5.

přetížitelné unární operátory jsou:

+ - ! (pouze logická negace) ~ ++ -- true false

Poznámka: I když true a false nejsou ve výrazech explicitně používány (a proto nejsou zahrnuty do tabulky priority v §12.4.2), jsou považovány za operátory, protože jsou vyvolány v několika kontextech výrazů.: Booleovské výrazy (§12.24) a výrazy zahrnující podmíněné (§12.18) a podmíněné logické operátory (§12.14). koncová poznámka

Poznámka: Operátor odpouštějící null (příponu !, §12.8.9) není operátorem, který lze přetížit. koncová poznámka

přetížitelné binární operátory jsou:

+  -  *  /  %  &  |  ^  <<  >>  ==  !=  >  <  <=  >=

Přetížit můžou být pouze výše uvedené operátory. Není možné přetížit přístup ke členům, vyvolání metody ani operátory =, &&, ||, ??, ?:, =>, checked, unchecked, new, typeof, default, asa is.

Pokud je binární operátor přetížen, odpovídající složený operátor přiřazení je také implicitně přetížen.

Příklad: Přetížení operátoru * je také přetížení operátoru *=. Toto je popsáno dále v §12.21. konec příkladu

Samotný operátor přiřazení (=) nelze přetížit. Přiřazení vždy provádí jednoduché uložení hodnoty do proměnné (§12.21.2).

Přetypování, například (T)x, jsou přetíženy poskytováním uživatelsky definovaných převodů (§10,5).

Poznámka: Uživatelem definované převody nemají vliv na chování operátorů is nebo as. koncová poznámka

Přístup k prvkům, jako je například a[x], není považován za přetížitelný operátor. Místo toho je uživatelsky definované indexování podporováno prostřednictvím indexerů (§15,9).

Ve výrazech se na operátory odkazuje pomocí zápisu operátoru a v deklaracích se na operátory odkazuje pomocí funkčního zápisu. Následující tabulka ukazuje vztah mezi operátorem a funkčním zápisem pro unární a binární operátory. V prvním záznamu "op" označuje jakýkoli přetížitelný unární prefixový operátor. Ve druhé položce označuje „op“ unární postfixové operátory ++ a --. Ve třetí položce "op» označuje jakýkoli přetížitelný binární operátor.

Poznámka: Příklad přetížení operátorů ++ a -- viz §15.10.2. koncová poznámka

notace operátoru funkční notace
«op» x operator «op»(x)
x «op» operator «op»(x)
x «op» y operator «op»(x, y)

Deklarace operátoru definované uživatelem vždy vyžadují, aby nejméně jeden z parametrů byl typu třídy nebo struktury, který obsahuje deklaraci operátoru.

Poznámka: Proto není možné, aby operátor definovaný uživatelem měl stejný podpis jako předdefinovaný operátor. koncová poznámka

Deklarace operátoru definované uživatelem nemohou upravit syntaxi, prioritu ani asociativitu operátoru.

Příklad: Operátor / je vždy binárním operátorem, vždy má úroveň priority zadanou v §12.4.2a vždy je asociativní. konec příkladu

Poznámka: Ačkoli je možné, aby operátor definovaný uživatelem provedl libovolné výpočty, implementace, které vytvářejí jiné výsledky než ty, které by bylo možné intuitivně očekávat, se důrazně nedoporučují. Například implementace operátoru == by měla porovnat dva operandy pro rovnost a vrátit odpovídající bool výsledek. koncová poznámka

Popisy jednotlivých operátorů v §12.9§12.21 určují předdefinované implementace operátorů a všechna další pravidla, která platí pro jednotlivé operátory. Popisy používají termíny přetížení unárního operátoru, přetížení binárního operátoru, číselné povýšenía definice zvednutých operátorů, které jsou nalezeny v následujících pododstavcích.

12.4.4 Rozlišení přetížení unárního operátoru

Operace formuláře «op» x nebo x «op», kde «op» je přetížitelný unární operátor a x je výraz typu X, je zpracován takto:

  • Sada kandidátských uživatelem definovaných operátorů poskytovaná X pro operaci operator «op»(x) je určena pomocí pravidel §12.4.6.
  • Pokud sada kandidátských uživatelem definovaných operátorů není prázdná, stane se tím sada kandidátských operátorů pro operaci. V opačném případě se předdefinované binární implementace operator «op», včetně jejich zdvižených forem, stanou sadou kandidátských operátorů pro danou operaci. Předdefinované implementace daného operátoru jsou uvedeny v popisu operátoru. Předdefinované operátory poskytované výčtovými nebo delegátovými typy jsou zahrnuty pouze v této sadě, pokud typ vazby – nebo základní typ, pokud se jedná o typ s možnou hodnotou null – jednoho z operandů je výčtový nebo delegátový typ.
  • Pravidla rozlišení přetížení §12.6.4 se použijí na sadu kandidátských operátorů, aby vybrali nejlepší operátor s ohledem na seznam argumentů (x)a tento operátor se stane výsledkem procesu řešení přetížení. Pokud se řešení přetížení nepodaří vybrat jeden nejlepší operátor, dojde k chybě doby vazby.

12.4.5 Rozlišení přetížení binárního operátoru

Operace formuláře x «op» y, kde «op» je přetížitelný binární operátor, x je výraz typu Xa y je výraz typu Y, je zpracován takto:

  • Sada kandidátních operátorů definovaných uživatelem, poskytovaných X a Y pro operaci operator «op»(x, y), je určena. Sada je tvořena sjednocením kandidátských operátorů poskytovaných X a kandidátskými operátory poskytovanými Y, z nichž každý je určen pravidly §12.4.6. Pro kombinovanou sadu jsou kandidáti sloučeni takto:
    • Pokud jsou X a Y zaměnitelné identity nebo pokud jsou X a Y odvozeny od společného základního typu, pak se sdílené kandidátské operátory vyskytují v kombinované sadě pouze jednou.
    • Pokud existuje převod identity mezi X a Y, operátor «op»Y poskytovaný Y má stejný návratový typ jako «op»X poskytované X a typy operandů «op»Y mají převod identity na odpovídající typy operandů «op»X, pak se v sadě vyskytuje pouze «op»X.
  • Pokud sada kandidátských uživatelem definovaných operátorů není prázdná, stane se tím sada kandidátských operátorů pro operaci. V opačném případě se předdefinované binární operator «op» implementace, včetně jejich zdvižených formulářů, stanou sadou kandidátských operátorů pro operaci. Předdefinované implementace daného operátoru jsou uvedeny v popisu operátoru. Pro předdefinované operátory výčtu a delegáta jsou operátory považovány pouze ty, které jsou definovány výčtem nebo typem delegáta, jenž je typem v době vazby pro jeden z operandů.
  • Pravidla rozlišení přetížení §12.6.4 se použijí na sadu kandidátských operátorů, aby vybrali nejlepší operátor s ohledem na seznam argumentů (x, y)a tento operátor se stane výsledkem procesu řešení přetížení. Pokud se při řešení přetížení nepodaří vybrat jediný nejlepší operátor, dojde k chybě při vazbě.

12.4.6 Kandidátské uživatelem definované operátory

Při zadání typu T a operace operator «op»(A), kde «op» je přetížitelný operátor a A je seznam argumentů, sada kandidátských uživatelem definovaných operátorů poskytovaná T pro operátor «op»(A) je určena takto:

  • Určete typ T₀. Pokud T je typ hodnoty null, T₀ je jeho základní typ; jinak se T₀ rovná T.
  • Pro všechna prohlášení operator «op» ve T₀ a pro všechny zdvižené formy těchto operátorů, pokud je s ohledem na seznam argumentů Apoužitelný alespoň jeden operátor (§12.6.4.2), pak sada kandidátských operátorů se skládá ze všech těchto použitelných operátorů v T₀.
  • V opačném případě, pokud je T₀object, je množina kandidátských operátorů prázdná.
  • Jinak je sada kandidátských operátorů poskytovaná T₀ tou sadou kandidátských operátorů, kterou poskytuje přímá základní třída T₀, nebo efektivní základní třída T₀, je-li T₀ parametr typu.

12.4.7 Číselné propagační akce

12.4.7.1 Obecné

Tato dílčí položka je informativní.

§12.4.7 a jeho dílčích ustanovení jsou souhrnem kombinovaného účinku:

  • pravidla pro implicitní číselné převody (§10.2.3);
  • pravidla pro lepší převod (§ 12.6.4.7); a
  • dostupné aritmetické operátory (§12.10), relační operátory (§12.12) a integrální logické operátory (§12.13.2).

Číselné povýšení se skládá z automatického provádění určitých implicitních převodů operandů předdefinovaných unárních a binárních číselných operátorů. Číselné povýšení není odlišný mechanismus, ale spíše účinek použití rozlišení přetížení na předdefinované operátory. Číselné povýšení konkrétně nemá vliv na vyhodnocení uživatelem definovaných operátorů, i když uživatelem definované operátory lze implementovat, aby se projevily podobné účinky.

Jako příklad číselného povýšení zvažte předdefinované implementace binárního operátoru *:

int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);

Při použití pravidel rozlišení přetížení (§12.6.4) u této sady operátorů je účinkem vybrat první z operátorů, pro které existují implicitní převody z typů operandu.

Příklad: Pro operaci b * s, kde b je byte a s je short, rozlišení přetížení vybere operator *(int, int) jako nejlepší operátor. Účinek je tedy, že b a s jsou převedeny na inta typ výsledku je int. Stejně tak pro operaci i * d, kde i je int a d je double, overload rozlišení vybere operator *(double, double) jako nejlepší operátor. konec příkladu

Konec informativního textu.

12.4.7.2 Unární číselné propagační akce

Tato dílčí položka je informativní.

U operandů předdefinovaných unárních operátorů +, -a ~ dochází k unárnímu číselnému povýšení. Unární číselné povýšení se jednoduše skládá z převodu operandů typu sbyte, byte, short, ushortnebo char na typ int. Kromě toho pro unární – operátor, unární číselné povýšení převede operandy typu uint na typ long.

Konec informativního textu.

12.4.7.3 Binární číselné povýšení

Tato dílčí položka je informativní.

Binární číselná propagace probíhá pro operandy předdefinovaných binárních operátorů +, -, *, /, %, &, |, ^, ==, !=, >, <, >=a <=. Binární číselné povýšení implicitně převede oba operandy na běžný typ, který v případě nerelačních operátorů se také stane typem výsledku operace. Binární číselné povýšení se skládá z použití následujících pravidel v pořadí, v jakém se zde zobrazují:

  • Pokud je jeden z operandů typu decimal, druhý operand se převede na typ decimal, nebo nastane chyba doby vazby, pokud je druhý operand typu float nebo double.
  • V opačném případě je-li jeden operand typu double, druhý operand je převeden na typ double.
  • V opačném případě je-li jeden operand typu float, druhý operand je převeden na typ float.
  • V opačném případě, pokud je jeden operand typu ulong, druhý operand je převeden na typ ulong, nebo dojde k chybě vazby, pokud druhý operand je type sbyte, short, intnebo long.
  • V opačném případě je-li jeden operand typu long, druhý operand je převeden na typ long.
  • V opačném případě, je-li jeden operand typu uint a druhý operand je typu sbyte, shortnebo int, oba operandy jsou převedeny na typ long.
  • V opačném případě je-li jeden operand typu uint, druhý operand je převeden na typ uint.
  • V opačném případě jsou oba operandy převedeny na typ int.

Poznámka: První pravidlo zakáže všechny operace, které kombinují typ decimal s typy double a float. Pravidlo vyplývá ze skutečnosti, že mezi typem decimal a typy double a float neexistují implicitní převody. koncová poznámka

Poznámka: Všimněte si také, že operand nemůže být typu ulong, pokud je druhý operand celočíselného typu se znaménkem. Důvodem je, že neexistuje žádný celočíselný typ, který by dokázal představit úplný rozsah hodnot ulong stejně dobře jako podepsané celočíselné typy. koncová poznámka

V obou výše uvedených případech lze výraz přetypování použít k explicitní převodu jednoho operandu na typ, který je kompatibilní s druhým operandem.

příklad: V následujícím kódu

decimal AddPercent(decimal x, double percent) =>
    x * (1.0 + percent / 100.0);

k chybě doby vazby dochází, protože decimal nelze vynásobit double. Tato chyba je vyřešena explicitním převodem druhého operandu na decimalnásledujícím způsobem:

decimal AddPercent(decimal x, double percent) =>
    x * (decimal)(1.0 + percent / 100.0);

koncového příkladu

Konec informativního textu.

12.4.8 Zvedané operátory

Lifted operátory umožňují, aby předdefinované a uživatelsky definované operátory, které pracují s nenulovými hodnotovými typy, mohly být také použity s nulovatelnými tvary těchto typů. Zvedané operátory se vytvářejí z předdefinovaných a uživatelem definovaných operátorů, které splňují určité požadavky, jak je popsáno v následujících případech:

  • Pro unární operátory +, ++, -, --, !(logická negace) a ~, existuje vyvýžená forma operátoru, pokud operand a typy výsledků jsou oba typy hodnot bez hodnoty null. Zvedená forma je konstruována přidáním jediného modifikátoru ? k typům operandu a výsledku. Zvýšený operátor vytvoří hodnotu null, pokud je operand null. Jinak zvednutý operátor rozbalí operand, použije základní operátor a zabalí výsledek.
  • Pro binární operátory +, -, *, /, %, &, |, ^, <<a >>, existuje nadzvednutá forma operátoru, pokud operand a typy výsledků jsou všechny typy hodnot, které nemají hodnotu null. Zdvižená forma je vytvořena přidáním jednoho modifikátoru ? do každého operandu a typu výsledku. Operátor typu "zvednutý" vytváří hodnotu null, pokud je jeden nebo oba operandy null (výjimku tvoří operátory & a | typu bool?, jak je popsáno v §12.13.5). Jinak zvednutý operátor rozbalí operandy, použije základní operátor a zabalí výsledek.
  • Pro operátory rovnosti == a !=existuje zdvižená forma operátoru, pokud jsou oba typy operandů nenulovými typy hodnot a typ výsledku je bool. Zdvižená forma je vytvořena přidáním jednoho modifikátoru ? ke každému typu operandu. Operátor lifted považuje dvě null hodnoty za stejné a null hodnotu za nerovnou s libovolnou ne-null hodnotou. Pokud oba operandy nejsounull, operátor zvednutím rozbalí operandy a použije podkladový operátor k vytvoření bool výsledku.
  • U relačních operátorů <, >, <=a >=existuje zdvižená forma operátoru, pokud jsou typy operandů nenulové hodnotové typy a pokud je typ výsledku bool. Zdvižená forma je vytvořena přidáním jednoho modifikátoru ? ke každému typu operandu. Operátor lifted vytvoří hodnotu false, pokud jsou jeden nebo oba operandy null. Jinak povýšený operátor rozvine operandy a použije podkladový operátor, aby vytvořil výsledek bool.

12.5 Vyhledávání členů

12.5.1 Obecné

Vyhledávání člena je proces, kdy je určen význam názvu v kontextu typu. Vyhledávání členů může být součástí vyhodnocení simple_name (§12.8.4) nebo member_access (§12.8.7) ve výrazu. Pokud simple_name nebo member_access nastane jako primary_expressioninvocation_expression (§12.8.10.2), je člen vyvolán.

Je-li člen metodou nebo událostí nebo je-li konstantní, pole nebo vlastnost buď typu delegáta (§20) nebo typ dynamic (§8.2.4), pak je člen vyvolán.

Vyhledávání členů bere v úvahu nejen název člena, ale také počet parametrů typu, které člen má a zda je člen přístupný. Pro účely vyhledávání členů mají obecné metody a vnořené obecné typy počet parametrů typu uvedených v příslušných deklaracích a všichni ostatní členové mají parametry nulového typu.

Vyhledání členu názvu N s argumenty typu K v typu T se zpracovává následujícím způsobem:

  • Nejprve se určí sada přístupných členů s názvem N:
    • Je-li T parametr typu, pak je sada sjednocení sad přístupných členů pojmenovaných N v každém z typů zadaných jako primární omezení nebo sekundární omezení (§15.2.5) pro T, spolu se sadou přístupných členů pojmenovaných N v object.
    • V opačném případě se sada skládá ze všech přístupných (§7.5) členů pojmenovaných N v T, včetně zděděných členů a přístupných členů pojmenovaných N v object. Je-li T konstruovaný typ, sada členů je získána nahrazením argumentů typu, jak je popsáno v §15.3.3. Členy, které obsahují modifikátor override, jsou ze sady vyloučeny.
  • Pokud je K nula, odeberou se všechny vnořené typy, jejichž deklarace zahrnují parametry typu. Pokud K není nula, odeberou se všechny členy s jiným počtem parametrů typu. Pokud je K nula, metody s parametry typu se neodeberou, protože proces odvozování typu (§12.6.3) může být schopen odvodit argumenty typu.
  • Dále, pokud je člen vyvolán, odeberou se ze sady všechny nevyvolatelné členy.
  • V dalším kroku se ze sady odeberou členové, kteří jsou skryti ostatními členy. Pro každý člen S.M v sadě, kde S je typ, ve kterém je člen M deklarován, jsou použita následující pravidla:
    • Pokud je M konstanta, pole, vlastnost, událost nebo člen výčtu, odeberou se ze sady všechny členy deklarované v základním typu S.
    • Je-li M deklarace typu, všechny typy, které nejsou deklarovány v základním typu S jsou ze sady odebrány a všechny deklarace typů se stejným počtem parametrů typu jako M deklarované v základním typu S jsou ze sady odebrány.
    • Pokud M je metoda, odeberou se ze sady všechny členy bez metody deklarované v základním typu S.
  • Dále se ze sady odeberou členové rozhraní, které jsou skryti členy třídy. Tento krok má účinek pouze v případě, že T je parametr typu a T má efektivní základní třídu jinou než object a neprázdnou efektivní sadu rozhraní (§15.2.5). Pro každý člen S.M v sadě, kde S je typ, ve kterém je M člen deklarován, použijí se následující pravidla, pokud S je deklarace třídy jiná než object:
    • Pokud M je konstanta, pole, vlastnost, událost, člen výčtu nebo deklarace typu, všechny členy deklarované v deklaraci rozhraní jsou ze sady odebrány.
    • Pokud M je metoda, odeberou se ze sady všechny členy bez metody deklarované v deklaraci rozhraní a všechny metody se stejným podpisem jako M deklarované v deklaraci rozhraní se ze sady odeberou.
  • Nakonec se po odebrání skrytých členů určí výsledek vyhledávání:
    • Pokud se sada skládá z jednoho členu, který není metodou, je tento člen výsledkem vyhledávání.
    • V opačném případě, pokud sada obsahuje pouze metody, pak tato skupina metod je výsledkem vyhledávání.
    • V opačném případě je vyhledávání nejednoznačné a vyskytne se chyba při vyhodnocování vazby.

Pro vyhledávání členů v typech jiných než jsou typové parametry a rozhraní a vyhledávání členů v rozhraních, která jsou přísně jednoduchá dědičnost (každé rozhraní v řetězci dědičnosti má přesně nula nebo jedno přímé základní rozhraní), je účinek pravidel vyhledávání jednoduše ten, že odvození členové skryjí základní členy se stejným názvem nebo podpisem. Takové vyhledávání s jednou dědičností nejsou nikdy nejednoznačné. Nejednoznačnosti, které mohou vzniknout z vyhledávání členů v rozhraních vícenásobné dědičnosti, jsou popsány v §18.4.6.

Poznámka: Tato fáze představuje pouze jeden druh nejednoznačnosti. Pokud hledání členů vede ke skupině metod, může další použití skupiny metod selhat z důvodu nejednoznačnosti, například popsané v §12.6.4.1 a §12.6.6.2. koncová poznámka

12.5.2 Základní typy

Pro účely vyhledávání členů je typ T považován za následující základní typy:

  • Pokud je Tobject nebo dynamic, T nemá žádný základní typ.
  • Pokud je Tenum_type, základní typy T jsou typy tříd System.Enum, System.ValueTypea object.
  • Pokud je T struktura typu , základní typy T jsou typy tříd System.ValueType a object.

    Poznámka: nullable_value_type je struct_type (§8.3.1). koncová poznámka

  • Je-li Ttyp třídy, základní typy T jsou základními třídami T, včetně typu třídy object.
  • Je-li Tinterface_type, základní typy T jsou základní rozhraní T a typ třídy object.
  • Pokud T je array_type, základní typy T jsou typy tříd System.Array a object.
  • Je-li Tdelegate_type, základní typy T jsou typy tříd System.Delegate a object.

12.6 Členy funkce

12.6.1 Obecné

Členy funkce jsou členy, které obsahují spustitelné příkazy. Funkční členové jsou vždy členy typů a nemohou být členy jmenných prostorů. Jazyk C# definuje následující kategorie členů funkce:

  • Metody
  • Vlastnosti
  • Dění
  • Indexování
  • Uživatelem definované operátory
  • Konstruktory instancí
  • Statické konstruktory
  • Finalizátory

S výjimkou finalizátorů a statických konstruktorů (které nelze explicitně vyvolat), příkazy obsažené ve členech funkce se provádějí prostřednictvím vyvolání členů funkce. Skutečná syntaxe pro zápis vyvolání člena funkce závisí na konkrétní kategorii člena funkce.

Seznam argumentů (§12.6.2) vyvolání člena funkce poskytuje skutečné hodnoty nebo proměnné odkazy na parametry člena funkce.

Vyvolání obecných metod může použít odvození typu k určení sady argumentů typu, které se mají předat metodě. Tento proces je popsán v §12.6.3.

Vyvolání metod, indexerů, operátorů a konstruktorů instancí využívá rozlišení přetížení k určení, která z kandidátských skupin členů funkce se má vyvolat. Tento proces je popsán v §12.6.4.

Jakmile je při vazbě identifikován určitý člen funkce, pravděpodobně pomocí rozlišení přetížení, skutečný proces vyvolání členu funkce za běhu programu je popsán v §12.6.6.

Poznámka: Následující tabulka shrnuje zpracování, které probíhá v konstruktech zahrnujících šest kategorií členů funkce, které lze explicitně vyvolat. V tabulce e, x, ya value označují výrazy klasifikované jako proměnné nebo hodnoty, T označuje výraz klasifikovaný jako typ, F je jednoduchý název metody a P je jednoduchý název vlastnosti.

Konstruovat Příklad Popis
Vyvolání metody F(x, y) Rozlišení přetížení se použije k výběru nejlepší metody F v obsahující třídě nebo struktuře. Metoda je vyvolána se seznamem argumentů (x, y). Pokud metoda není static, výraz instance je this.
T.F(x, y) K výběru nejlepší metody F ve třídě nebo struktuře Tse používá rozlišení přetížení. K chybě doby vazby dochází, pokud metoda není static. Metoda je vyvolána se seznamem argumentů (x, y).
e.F(x, y) Rozlišení přetížení se použije k výběru nejlepší metody F ve třídě, struktuře nebo rozhraní zadaném typem e. K chybě doby vazby dojde, pokud je metoda static. Metoda je vyvolána pomocí výrazu instance e a seznamu argumentů (x, y).
Přístup k vlastnostem P Přístupový prvek typu get vlastnosti P ve vnitřní třídě nebo struktuře je vyvolán. Pokud je P jen pro zápis, dojde k chybě v době kompilace. Pokud P není static, výraz instance je this.
P = value Nastavovací metoda vlastnosti P v obsahující třídě nebo struktuře je vyvolána se seznamem argumentů (value). Pokud je P jen pro čtení, dojde k chybě v době kompilace. Pokud P není static, výraz instance je this.
T.P Přístupový objekt get vlastnosti P ve třídě nebo struktuře T je vyvolán. K chybě v době kompilace dochází, pokud P není static nebo je P jen pro zápis.
T.P = value Set akcesor vlastnosti P ve třídě nebo struktuře T je vyvolán se seznamem argumentů (value). K chybě v době kompilace dochází, pokud P není static nebo je P jen pro čtení.
e.P Get přístup k vlastnosti P v třídě, struktuře nebo rozhraní zadaném typem E je vyvolán výrazem instance e. K chybě při vazbě dochází, pokud je Pstatic nebo je P určeno pouze pro zápis.
e.P = value Nastavovací metoda vlastnosti P ve třídě, struktuře nebo rozhraní určeném typem E se vyvolává s výrazem instance e a seznamem argumentů (value). Dochází k chybě doby vazby, pokud je Pstatic, nebo pokud je P určeno pouze pro čtení.
Přístup k událostem E += value Vyvolá se doplněk události E v obsahující třídě nebo struktuře. Pokud E není static, výraz instance je this.
E -= value Přístup k odebrání události E ve včleněné třídě nebo struktuře je vyvolán. Pokud E není static, výraz instance je this.
T.E += value Přidání přístupového členu události E ve třídě nebo struktuře T je vyvoláno. K chybě při vazbě dochází, pokud E není static.
T.E -= value Odebrání přístupového objektu události E ve třídě nebo struktuře T je vyvoláno. K chybě doby vazby dochází, pokud E není static.
e.E += value Přidávací přístupový prvek události E ve třídě, struktuře nebo rozhraní daném typem E je vyvolán s výrazem instance e. Dochází k chybě doby vazby, pokud je Estatic.
e.E -= value Odebrání přístupového objektu události E ve třídě, struktuře nebo rozhraní zadaném typem E je vyvoláno výrazem instance e. K chybě při vazbě dochází, pokud je Estatic.
Přístup k indexeru e[x, y] Výběr nejlepšího indexeru ve třídě, struktuře nebo rozhraní určených typem ese provádí pomocí rozlišení přetížení. Přístupový objekt get indexeru je vyvolán výrazem instance e a seznamem argumentů (x, y). Chyba při vazebním čase nastane, pokud je indexer pouze pro zápis.
e[x, y] = value Rozlišení přetížení se použije k výběru nejlepšího indexeru ve třídě, struktuře nebo rozhraní zadaném typem e. Objekt set indexeru se vyvolá pomocí výrazu instance e a seznamu argumentů (x, y, value). Pokud je indexer pouze pro čtení, nastane chyba při určování doby vazby.
Vyvolání operátoru -x Rozlišení přetížení se použije k výběru nejlepšího unárního operátoru ve třídě nebo struktuře určené typem x. Vybraný operátor je vyvolán se seznamem argumentů (x).
x + y Rozlišení přetížení se použije k výběru nejlepšího binárního operátoru ve třídách nebo strukturách zadaných typy x a y. Vybraný operátor je vyvolán se seznamem argumentů (x, y).
Vyvolání konstruktoru instance new T(x, y) Rozlišení přetížení se použije k výběru nejlepšího konstruktoru instance ve třídě nebo ve struktuře T. Konstruktor instance je vyvolán se seznamem argumentů (x, y).

koncová poznámka

12.6.2 Seznamy argumentů

12.6.2.1 Obecné

Každý člen funkce a vyvolání delegáta obsahuje seznam argumentů, který poskytuje skutečné hodnoty nebo odkazy na proměnné pro parametry člena funkce. Syntaxe pro zadání seznamu argumentů vyvolání člena funkce závisí na kategorii člena funkce:

  • Například pro konstruktory, metody, indexery a delegáty jsou argumenty uvedeny jako argument_list, jak je popsáno níže. Při volání přístupového modifikátoru indexeru seznam argumentů pro indexery navíc obsahuje výraz určený jako pravý operand přiřazovacího operátoru.

    Poznámka: Tento další argument se nepoužívá pro rozlišení přetížení, právě při vyvolání přístupového objektu sady. koncová poznámka

  • Pro vlastnosti je seznam argumentů prázdný při vyvolání přístupového objektu get a skládá se z výrazu zadaného jako správný operand operátoru přiřazení při vyvolání přístupového objektu set.
  • V případě událostí se seznam argumentů skládá z výrazu zadaného jako pravý operand operátoru += nebo -=.
  • Pro uživatelem definované operátory se seznam argumentů skládá z jednoho operandu unárního operátoru nebo dvou operandů binárního operátoru.

Argumenty vlastností (§15,7) a události (§15,8) jsou vždy předány jako parametry hodnoty (§15.6.2.2). Argumenty uživatelem definovaných operátorů (§15.10) se vždy předávají jako parametry hodnot (§15.6.2.2) nebo vstupní parametry (§9.2.8). Argumenty indexerů (§15,9) jsou vždy předány jako parametry hodnot (§15.6.2.2), vstupní parametry (§9.2.8) nebo pole parametrů (§15.6.2.4). Výstupní a referenční parametry nejsou podporovány pro tyto kategorie členů funkce.

Argumenty konstruktoru instance, metody, indexeru nebo vyvolání delegáta jsou zadány jako argument_list:

argument_list
    : argument (',' argument)*
    ;

argument
    : argument_name? argument_value
    ;

argument_name
    : identifier ':'
    ;

argument_value
    : expression
    | 'in' variable_reference
    | 'ref' variable_reference
    | 'out' variable_reference
    ;

argument_list se skládá z jednoho nebo více argumentů, oddělených čárkami. Každý argument se skládá z volitelného argument_name následovaného argument_value. Argument s argument_name se označuje jako pojmenovaný argument, zatímco argument bez argument_name je poziční argument.

argument_value může mít jednu z následujících forem:

  • Výraz označující, že argument je předán jako parametr hodnoty nebo je transformován do vstupního parametru a následně předán tak, jak je určeno (§12.6.4.2 a popsán v §12.6.2.3.
  • Klíčové slovo in následované variable_reference (§9,5), označující, že argument je předán jako vstupní parametr (§15.6.2.3.2). Proměnná musí být rozhodně přiřazena (§9,4) před předáním jako vstupního parametru.
  • Klíčové slovo ref následované variable_reference (§9,5), označující, že argument je předán jako referenční parametr (§15.6.2.3.3). Proměnná musí být rozhodně přiřazena (§9.4) před předáním jako referenčního parametru.
  • Klíčové slovo out následované variable_reference (§9,5), označující, že argument je předán jako výstupní parametr (§15.6.2.3.4). Proměnná je považována za rozhodně přiřazenou (§9,4) po vyvolání člena funkce, ve kterém je proměnná předána jako výstupní parametr.

Formulář určuje režimu předávání parametrů argumentu: hodnota, vstupní, odkaznebo výstupní. Jak je však uvedeno výše, může být argument s režimem předávání hodnot transformován do jednoho s režimem předávání vstupu.

Předání nestálého pole (§15.5.4) jako vstupní, výstupní nebo referenční parametr způsobí upozornění, protože pole nemusí být považováno za nestálé metodou vyvolání.

12.6.2.2 Odpovídající parametry

Pro každý argument v seznamu argumentů musí být odpovídající parametr ve funkčním členu nebo delegátovi, který se volá.

Seznam parametrů použitý dále je určen následujícím způsobem:

  • U virtuálních metod a indexerů definovaných ve třídách je seznam parametrů vybrán z první deklarace nebo přepsání člena funkce nalezen při spuštění se statickým typem příjemce a vyhledáváním v jeho základních třídách.
  • Pro částečné metody se použije seznam parametrů definující deklarace částečné metody.
  • Pro všechny ostatní členy funkce a delegáty existuje pouze jeden seznam parametrů, který se používá.

Pozice argumentu nebo parametru je definována jako počet argumentů nebo parametrů před ním v seznamu argumentů nebo seznamu parametrů.

Odpovídající parametry pro argumenty členů funkce jsou vytvořeny takto:

  • Argumenty v argument_list u konstruktorů instancí, metod, indexerů a delegátů:
    • Poziční argument, kde se parametr vyskytuje na stejné pozici v seznamu parametrů, odpovídá tomuto parametru, pokud parametr není polem parametrů a člen funkce je vyvolán v rozšířené podobě.
    • Poziční argument člena funkce s polem parametrů vyvolaným v rozšířené podobě, který se vyskytuje na pozici pole parametrů v seznamu parametrů nebo za tím, odpovídá prvku v matici parametrů.
    • Pojmenovaný argument odpovídá parametru se stejným názvem v seznamu parametrů.
    • U indexerů při vyvolání přístupového objektu set výraz zadaný jako pravý operand operátoru přiřazení odpovídá implicitnímu value parametru deklarace objektu set accessor.
  • Při vyvolání přístupového objektu get nejsou pro vlastnosti žádné argumenty. Při vyvolání přístupového objektu set odpovídá výraz zadaný jako správný operand operátoru přiřazení implicitnímu parametru hodnoty deklarace objektu set accessor.
  • Pro uživatelem definované unární operátory (včetně převodů) jeden operand odpovídá jedinému parametru deklarace operátoru.
  • U uživatelem definovaných binárních operátorů levý operand odpovídá prvnímu parametru a pravý operand odpovídá druhému parametru deklarace operátoru.
  • Nepojmenovaný argument neodpovídá žádnému parametru, pokud následuje pojmenovaný argument na nesprávné pozici nebo pojmenovaný argument, který odpovídá poli parametrů.

    Poznámka: Tím se zabrání vyvolání void M(bool a = true, bool b = true, bool c = true); pomocí M(c: false, valueB);. První argument se používá mimo pozici (argument se používá na první pozici, ale parametr s názvem c je ve třetí pozici), takže by se měly pojmenovat následující argumenty. Jinými slovy, neukončující pojmenované argumenty jsou povoleny pouze v případě, že název a pozice způsobí nalezení stejného odpovídajícího parametru. koncová poznámka

12.6.2.3 Vyhodnocení seznamů argumentů za běhu

Během zpracování běhu vyvolání člena funkce (§12.6.6), výrazy nebo proměnné odkazy na seznam argumentů se vyhodnocují v pořadí zleva doprava takto:

  • Pokud je režim předávání parametru hodnota pro argument hodnoty.

    • výraz argumentu se vyhodnotí a provede se implicitní převod (§10.2) na odpovídající typ parametru. Výsledná hodnota se stane počáteční hodnotou parametru hodnoty ve volání člena funkce.

    • jinak je režim předávání parametru vstup. Pokud je argument odkazem na proměnnou a existuje převod identity (§10.2.2) mezi typem argumentu a typem parametru, výsledné umístění úložiště se stane umístěním úložiště reprezentovaným parametrem v vyvolání člena funkce. V opačném případě se vytvoří umístění úložiště se stejným typem jako odpovídající parametr. Výraz argumentu se vyhodnotí a provede se implicitní převod (§10,2) na odpovídající typ parametru. Výsledná hodnota je uložena v daném umístění úložiště. Toto umístění úložiště je reprezentováno vstupním parametrem při vyvolání člena funkce.

      Příklad: S ohledem na následující deklarace a volání metody:

      static void M1(in int p1) { ... }
      int i = 10;
      M1(i);         // i is passed as an input argument
      M1(i + 5);     // transformed to a temporary input argument
      

      Ve volání metody M1(i) se i předává jako vstupní argument, protože je klasifikován jako proměnná a má stejný typ int jako vstupní parametr. Ve volání metody M1(i + 5) se vytvoří nepojmenovaná int proměnná, inicializuje se s hodnotou argumentu a pak se předá jako vstupní argument. Viz §12.6.4.2 a §12.6.4.4.

      závěr příkladu

  • U vstupního, výstupního nebo referenčního argumentu se vyhodnotí odkaz na proměnnou a výsledné umístění úložiště se stane umístěním úložiště reprezentovaným parametrem při vyvolání člena funkce. U vstupního nebo referenčního argumentu musí být proměnná rozhodně přiřazena v okamžiku volání metody. Pokud je odkaz na proměnnou uveden jako výstupní argument nebo jako prvek pole reference_type, provede se kontrola za běhu programu, která zajistí, že typ prvku pole je identický s typem parametru. Pokud tato kontrola selže, vyvolá se System.ArrayTypeMismatchException.

Poznámka: tato kontrola za běhu je vyžadována z důvodu kovariance pole (§17.6). koncová poznámka

příklad: V následujícím kódu

class Test
{
    static void F(ref object x) {...}

    static void Main()
    {
        object[] a = new object[10];
        object[] b = new string[10];
        F(ref a[0]); // Ok
        F(ref b[1]); // ArrayTypeMismatchException
    }
}

druhé vyvolání F způsobí vyvolání System.ArrayTypeMismatchException, protože skutečný typ prvku b je string a nikoli object.

ukončit příklad

Metody, indexery a konstruktory instancí mohou deklarovat, že nejpravější parametr je parametrické pole (§15.6.2.4). Tyto členy funkce jsou vyvolány buď v jejich normální podobě, nebo v rozbalené podobě v závislosti na tom, která je použitelná (§12.6.4.2):

  • Pokud je člen funkce s polem parametrů vyvolán v normální podobě, argument zadaný pro pole parametrů musí být jediný výraz, který je implicitně konvertibilní (§10.2) na typ pole parametru. V tomto případě pole parametrů funguje přesně jako parametr hodnoty.
  • Pokud je člen funkce s polem parametrů vyvolán ve své rozšířené podobě, vyvolání musí pro pole parametrů zadat nula nebo více pozičních argumentů, kde každý argument je implicitně konvertibilní (§10.2) na typ prvku pole parametrů. V tomto případě vyvolání vytvoří instanci typu pole parametru s délkou odpovídající počtu argumentů, inicializuje prvky instance pole s danými hodnotami argumentu a použije nově vytvořenou instanci matice jako skutečný argument.

Výrazy seznamu argumentů se vždy vyhodnocují v textovém pořadí.

příklad: Tedy příklad

class Test
{
    static void F(int x, int y = -1, int z = -2) =>
        Console.WriteLine($"x = {x}, y = {y}, z = {z}");

    static void Main()
    {
        int i = 0;
        F(i++, i++, i++);
        F(z: i++, x: i++);
    }
}

vytvoří výstup.

x = 0, y = 1, z = 2
x = 4, y = -1, z = 3

konec příkladu

Když je člen funkce s polem parametrů vyvolán ve své rozšířené podobě s alespoň jedním rozbaleným argumentem, vyvolání se zpracuje, jako by byl výraz pro vytvoření pole s inicializátorem pole (§12.8.17.5) vložen kolem rozbalených argumentů. Prázdné pole je předáno, pokud neexistují žádné argumenty pro pole parametrů; není zadáno, zda je odkaz předán do nově přiděleného nebo existujícího prázdného pole.

Příklad: Vzhledem k deklaraci

void F(int x, int y, params object[] args);

následující vyvolání rozšířené formy metody

F(10, 20, 30, 40);
F(10, 20, 1, "hello", 3.0);

přesně odpovídá

F(10, 20, new object[] { 30, 40 });
F(10, 20, new object[] { 1, "hello", 3.0 });

konec příkladu

Pokud argumenty vynecháte ze člena funkce s odpovídajícími volitelnými parametry, výchozí argumenty deklarace člena funkce se implicitně předají. (To může zahrnovat vytvoření umístění úložiště, jak je popsáno výše.)

Poznámka: Protože jsou vždy konstantní, jejich vyhodnocení nebude mít vliv na vyhodnocení zbývajících argumentů. koncová poznámka

12.6.3 Odvození typu

12.6.3.1 Obecné

Pokud je volána obecná metoda bez zadání argumentů typu, proces odvození typu se pokusí odvodit argumenty typu pro volání. Přítomnost odvození typu umožňuje použití pohodlnější syntaxe pro volání obecné metody a umožňuje programátorům vyhnout se zadávání redundantních informací o typu.

Příklad :

class Chooser
{
    static Random rand = new Random();

    public static T Choose<T>(T first, T second) =>
        rand.Next(2) == 0 ? first : second;
}

class A
{
    static void M()
    {
        int i = Chooser.Choose(5, 213); // Calls Choose<int>
        string s = Chooser.Choose("apple", "banana"); // Calls Choose<string>
    }
}

Prostřednictvím odvození typu jsou argumenty typu int a string určeny z argumentů metody.

konec příkladu

K odvození typu dochází jako součást zpracování vazební doby při vyvolání metody (§12.8.10.2) a probíhá před krokem řešení přetížení volání. Pokud je v vyvolání metody zadána konkrétní skupina metod a v rámci vyvolání metody nejsou zadány žádné argumenty typu, použije se pro každou obecnou metodu ve skupině metod odvození typu. Pokud je odvození typu úspěšné, použijí se argumenty odvozeného typu k určení typů argumentů pro následné řešení přetížení. Pokud rozlišení přetížení zvolí obecnou metodu, která se má vyvolat, pak odvozené argumenty typu se použijí jako argumenty typu pro vyvolání. Pokud odvození typu pro konkrétní metodu selže, tato metoda se nezaúčastní rozlišení přetížení. Selhání odvození typu samo o sobě chybu v době vazby nezpůsobí. Často však vede k chybě v době vazby při řešení přetížení a pak se nepodaří najít žádné použitelné metody.

Pokud každý zadaný argument neodpovídá přesně jednomu parametru v metodě (§12.6.2.2), nebo existuje nepovinný parametr bez odpovídajícího argumentu, pak odvození okamžitě selže. V opačném případě předpokládejme, že obecná metoda má následující podpis:

Tₑ M<X₁...Xᵥ>(T₁ p₁ ... Tₓ pₓ)

Při volání metody formuláře M(E₁ ...Eₓ) úkolem odvození typu je najít jedinečné argumenty typu S₁...Sᵥ pro každý z parametrů typu X₁...Xᵥ tak, aby volání M<S₁...Sᵥ>(E₁...Eₓ) bylo platné.

Proces odvození typu je popsán níže jako algoritmus. Odpovídající kompilátor může být implementován pomocí alternativního přístupu, pokud dosáhne stejného výsledku ve všech případech.

Během procesu odvozování je každý typový parametr Xᵢ buď upevněn na konkrétní typ Sᵢ, nebo nezafixován s přidruženou sadou omezení. Každé z omezení je určitý typ T. Na začátku není každá proměnná typu Xᵢ vázána s prázdnou množinou omezení.

Odvození typu probíhá ve fázích. Každá fáze se pokusí odvodit argumenty typu pro více proměnných typů na základě zjištění předchozí fáze. První fáze provede několik počátečních odvozování hranic, zatímco druhá fáze nastavuje proměnné typu na konkrétní typy a vyvozuje další hranice. Druhá fáze se může několikrát opakovat.

Poznámka: Odvození typu se používá také v jiných kontextech, včetně převodu skupin metod (§12.6.3.14) a nalezení nejlepšího společného typu sady výrazů (§12.6.3.15). koncová poznámka

12.6.3.2 První fáze

Pro každý argument metody Eᵢ:

  • Je-li anonymní funkcí, je odvození typu explicitního typu parametru (§12.6.3.8) zdo
  • V opačném případě, pokud má typ a odpovídající parametr je parametr hodnoty (§15.6.2.2) pak dolní (§12.6.3.10) se vytvoří zdo.
  • Jinak, pokud Eᵢ má typ U a odpovídající parametr je referenčním parametrem (§15.6.2.3.3) nebo výstupní parametr (§15.6.2 .3.4) je přesný odvození (§12.6.3.9) odUdoTᵢ.
  • Jinak, pokud Eᵢ má typ U a odpovídajícím parametrem je vstupní parametr (§15.6.2.3.2) a Eᵢ je vstupním argumentem, přesný odvození (§12.6.3.9) je zUnaTᵢ.
  • V opačném případě, pokud Eᵢ má typ U a odpovídající parametr je vstupní parametr (§15.6.2.3.2) pak dolní mez odvození (§12.6.3.10) se zUTᵢ.
  • Jinak se pro tento argument nevyvozuje žádná odvození.

12.6.3.3 Druhá fáze

Druhá fáze pokračuje takto:

  • Všechny nefixované proměnné typuXᵢ, které nejsou závislé na (§12.6.3.6), jsou všechny Xₑ pevné (§12.6.3.12).
  • Pokud neexistují žádné takové proměnné typu, všechny neurčené proměnné typuXᵢ jsou určeny, pro které platí vše z následujícího:
    • Existuje alespoň jedna proměnná typu Xₑ, která závisí naXᵢ
    • Xᵢ má neprázdnou sadu hranic.
  • Pokud žádné takové proměnné typu neexistují a stále existují neurčené proměnné typu, odvození typu selže.
  • Jinak pokud neexistují žádné další nepřipravené proměnné typu, odvození typu proběhne úspěšně.
  • Jinak pro všechny argumenty Eᵢ s odpovídajícím typem parametru Tᵢ, kde výstupní typy (§12.6.3.5) obsahují nepřipravené proměnné typuXₑ, ale vstupní typy (§12.6.3.4) ne, odvození typu výstupu (§12.6.3.7) je zEᵢdoTᵢ. Pak se opakuje druhá fáze.

12.6.3.4 Vstupní typy

Pokud je E skupina metod nebo implicitně zadaná anonymní funkce a T je typ delegáta nebo výrazový strom, pak jsou všechny typy parametrů Tvstupní typyEs typemT.

12.6.3.5 Výstupní typy

Pokud E je skupina metod nebo anonymní funkce a T je typ delegáta nebo stromu výrazů, návratový typ T je typ výstupuEo typuT.

12.6.3.6 Závislost

nefixovaná proměnná typuXᵢzávisí přímo na nefixované proměnné typuXₑ, pokud nějaký argument Eᵥ s typem TᵥXₑ nastává ve vstupním typu Eᵥ s typem Tᵥ a Xᵢ se vyskytuje ve výstupním typu Eᵥ s typem Tᵥ.

Xₑ závisí naXᵢ, pokud Xₑzávisí přímo naXᵢ nebo jestli Xᵢzávisí přímo naXᵥ a Xᵥzávisí naXₑ. Proto "závisí na" je tranzitivním, ale nikoli reflexivním uzávěrem "závisí přímo na".

12.6.3.7 Odvození výstupního typu

odvození výstupního typu se z výrazu E typu T následujícím způsobem:

  • Je-li anonymní funkcí s odvozeným návratovým typem (§12.6.3.13) a je typ delegování nebo stromový typ výrazu s návratovým typem odvození (§ 12.6.3.10) je zdo.
  • V opačném případě, pokud je skupina metod a je typ delegáta nebo stromový typ výrazu s typy parametrů a návratového typu , a přetížení rozlišení s typy vrací jednu metodu s návratovým typem , pak se provede dolní meze odvozování zdo.
  • V opačném případě pokud je výraz s typem , odvození se vytvoří zdo.
  • Jinak se nedělají žádné závěry.

12.6.3.8 Explicitní odvození typu parametru

explicitní odvození typu parametru sez výrazu E typ T následujícím způsobem:

  • Pokud je E explicitně zadaná anonymní funkce s typy parametrů U₁...Uᵥ a T je typ delegovaného typu nebo stromu výrazů s typy parametrů V₁...Vᵥ pak pro každý Uᵢpřesný odvozování (§12.6.3.9) se zUᵢ odpovídající Vᵢ.

12.6.3.9 Přesné odvozy

Následujícím způsobem je provedeno přesné odvozeníz typu Una typ V:

  • Pokud je V jedním z neopravenýchXᵢ, U se přidá do sady přesných hranic pro Xᵢ.
  • V opačném případě se sady V₁...Vₑ a U₁...Uₑ určují ověřením, zda se vztahuje některý z následujících případů:
    • V je typ pole V₁[...] a U je typ pole U₁[...] stejného pořadí.
    • V je typ V₁? a U je typ U₁
    • V je konstruovaný typ C<V₁...Vₑ> a U je vytvořený typ C<U₁...Uₑ>
      Pokud se některý z těchto případů použije, provede se přesné odvození z každého Uᵢ pro odpovídající Vᵢ.
  • Jinak se nevyvodí žádné závěry.

12.6.3.10 Odvození dolní meze

Odvození dolní mez z typu Una typ V je provedeno takto:

  • Pokud je V jedním z neopravenýchXᵢ, U se přidá do sady dolních mezí pro Xᵢ.
  • V opačném případě pokud je V typ V₁? a U je typ U₁? pak se odvozuje dolní mez z U₁ do V₁.
  • V opačném případě se sady U₁...Uₑ a V₁...Vₑ určují kontrolou, jestli platí některý z následujících případů:
    • V je typ pole V₁[...]a U je typ pole U₁[...]stejného pořadí.
    • V je jedním z IEnumerable<V₁>, ICollection<V₁>, IReadOnlyList<V₁>>, IReadOnlyCollection<V₁> nebo IList<V₁> a U je jednorozměrný typ pole U₁[]
    • V je vytvořený class, struct, interface nebo delegate typ C<V₁...Vₑ> a existuje jedinečný typ C<U₁...Uₑ> tak, aby U (nebo pokud U je typ parameter, jeho efektivní základní třída nebo jakýkoli člen jeho efektivní sady rozhraní) je identický, inherits z (přímo nebo nepřímo) nebo implementuje (přímo nebo nepřímo) C<U₁...Uₑ>.
    • (Omezení "jedinečnosti" znamená, že v rozhraní C<T>{} class U: C<X>, C<Y>{}, při odvozování z U do C<T>, neprobíhá žádné odvozování, protože U₁ může být X nebo Y.)
      Pokud se některý z těchto případů použije, provede se odvozování z každého Uᵢ na odpovídající Vᵢ následujícím způsobem:
    • Pokud Uᵢ není známo, že se jedná o referenční typ, provede se přesný závěr.
    • Jinak, pokud je U typ pole, provede se odvození dolní meze .
    • Jinak pokud je VC<V₁...Vₑ>, odvozování závisí na parametru typu i-thC:
      • Pokud je kovariantní, provede se odvození spodní meze .
      • Pokud je kontravariantní, je provedeno odvození horní hranice .
      • Pokud je invariantní, provede se přesný závěr.
  • Jinak se nevyvozují žádné závěry.

12.6.3.11 Odvození horní hranice

Horní mez vyplývající z typu Una typ V je stanovena následujícím způsobem:

  • Pokud je V jedním z neopravenýchXᵢ, přidá se U do sady horních hranic pro Xᵢ.
  • V opačném případě se sady V₁...Vₑ a U₁...Uₑ určují ověřením, jestli platí některý z následujících případů:
    • U je typ pole U₁[...]a V je typ pole V₁[...]stejného pořadí.
    • U je jedním z IEnumerable<Uₑ>, ICollection<Uₑ>, IReadOnlyList<Uₑ>, IReadOnlyCollection<Uₑ> nebo IList<Uₑ> a V je jednorozměrný typ pole Vₑ[]
    • U je typ U1? a V je typ V1?
    • U je vytvořený typ třídy, struktury, rozhraní nebo delegáta C<U₁...Uₑ> a V je typ class, struct, interface nebo delegate, který je identical na, inherits z (přímo nebo nepřímo), nebo implementuje (přímo nebo nepřímo) jedinečný typ C<V₁...Vₑ>
    • (Omezení "jedinečnost" znamená, že vzhledem k C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}rozhraní se při odvozování z C<U₁> do V<Q>nevyvozuje žádná odvození . Odvozování se neprovozuje z U₁ do X<Q> nebo Y<Q>.)
      Pokud se některý z těchto případů použije, provede se odvozování z každého Uᵢ na odpovídající Vᵢ následujícím způsobem:
    • Pokud Uᵢ není známo, že se jedná o typ odkazu, provede se přesná odvození
    • Jinak, pokud je V typ pole, vytvoří se odvozování horní hranice pro .
    • Jinak pokud je UC<U₁...Uₑ>, odvozování závisí na parametru typu i-thC:
      • Pokud je kovariantní, provede se odvození horní hranice .
      • Pokud je kontravariantní, provádí se odvození dolní hranice .
      • Pokud je invariantní, provede se přesně odvození.
  • Jinak se nedělají žádné závěry.

12.6.3.12 Oprava

neuzamčená typová proměnnáXᵢ se sadou hranic je uzamčená následujícím způsobem:

  • Sada kandidátských typůUₑ počíná jako soubor všech typů v rámci hranic pro Xᵢ.
  • Každá mez Xᵢ je postupně zkoumána: Pro každou přesnou mez U Xᵢ jsou z kandidátské sady odebrány všechny typy Uₑ, které nejsou identické s U. Pro každou dolní mez U z Xᵢ jsou z kandidátské sady odebrány všechny typy Uₑ, ke kterým není implicitní převod z U. Pro každou horní mez U Xᵢ jsou z kandidátské sady odebrány všechny typy Uₑ, ze kterých neexistuje implicitní převod na U.
  • Pokud mezi zbývajícími kandidátskými typy Uₑ existuje jedinečný typ V, na který existuje implicitní převod ze všech ostatních kandidátských typů, Xᵢ je pevně nastaven na V.
  • V opačném případě se odvození typu nezdaří.

12.6.3.13 Odvozený návratový typ

Odvozený návratový typ anonymní funkce F se používá při odvozování typu a rozlišení přetížení. Odvozený návratový typ lze určit pouze pro anonymní funkci, kde jsou známy všechny typy parametrů, a to buď proto, že jsou explicitně zadány, poskytnuty prostřednictvím převodu anonymní funkce, nebo odvozeny při odvozování typu během vyhodnocování volání obklopující obecné metody.

účinný návratový typ je odvozen a určen následujícím způsobem:

  • Pokud je tělo F výrazem , který má typ, odvozený efektivní návratový typ F je typ tohoto výrazu.
  • Pokud tělo F je blokem typu a sada výrazů v příkazech bloku return má nejvhodnější společný typ T (§12.6.3.15), pak je odvozený efektivní návratový typ FT.
  • V opačném případě nelze efektivní návratový typ odvodit pro F.

odvozený návratový typ je určen takto:

  • Je-li F asynchronní a tělo F je buď výraz klasifikovaný jako nic (§12.2), nebo blok, kde žádné return příkazy nemají výrazy, odvozený návratový typ je «TaskType» (§15.15.1).
  • Pokud je F asynchronní a má odvozený účinný návratový typ T, odvozený návratový typ je «TaskType»<T>»(§15.15.1).
  • Pokud F není asynchronní a má odvozený účinný návratový typ T, odvozený návratový typ je T.
  • V opačném případě nelze návratový typ odvodit pro F.

Příklad: Jako příklad odvození typu zahrnujícího anonymní funkce zvažte metodu rozšíření Select deklarovanou ve třídě System.Linq.Enumerable:

namespace System.Linq
{
    public static class Enumerable
    {
        public static IEnumerable<TResult> Select<TSource,TResult>(
            this IEnumerable<TSource> source,
            Func<TSource,TResult> selector)
        {
            foreach (TSource element in source)
            {
                yield return selector(element);
            }
        }
   }
}

Za předpokladu, že obor názvů System.Linq byl importován pomocí direktivy using namespace a je dána třída Customer s vlastností Name typu string, lze použít metodu Select k výběru názvů seznamu zákazníků.

List<Customer> customers = GetCustomerList();
IEnumerable<string> names = customers.Select(c => c.Name);

Vyvolání metody rozšíření (§12.8.10.3) Select se zpracuje přepsáním na vyvolání statické metody.

IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);

Vzhledem k tomu, že argumenty typu nebyly explicitně zadány, je odvození typu použito k odvození argumentů typu. Za prvé, argument customers souvisí se zdrojovým parametrem, odvozuje TSource být Customer. Poté, pomocí výše popsaného procesu odvozování typu anonymní funkce, je c přiřazen typ Customer, a výraz c.Name souvisí s návratovým typem parametru selektoru, přičemž se odvozuje, že TResult je string. Vyvolání je tedy rovnocenné

Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)

a výsledek je typu IEnumerable<string>.

Následující příklad ukazuje, jak odvození typu anonymních funkcí umožňuje "proudění" informací o typech mezi argumenty při volání obecné metody. Při použití následující metody a vyvolání:

class A
{
    static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2)
    {
        return f2(f1(value));
    }

    static void M()
    {
        double hours = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalHours);
    }
}

odvození typu pro vyvolání pokračuje následovně: Nejprve je argument „1:15:30“ přičítán k parametru hodnoty, což vede k odvození, že X je řetězec. Parametr první anonymní funkce, s, má odvozený typ stringa výraz TimeSpan.Parse(s) souvisí s návratovým typem f1, odvozuje Y jako System.TimeSpan. Nakonec, parametr druhé anonymní funkce, t, má odvozený typ System.TimeSpan, a výraz t.TotalHours souvisí s návratovým typem f2, a odvozuje se, že Z je double. Výsledkem vyvolání je tedy typ double.

konec příkladu

12.6.3.14 Odvození typu pro převod skupin metod

Podobně jako volání obecných metod se použije také odvození typu, pokud je skupina metod M obsahující obecnou metodu převedena na daný typ delegáta D (§10.8). Daná metoda

Tₑ M<X₁...Xᵥ>(T₁ x₁ ... Tₑ xₑ)

a skupina metod M přiřazená k typu delegáta D má úkol odvození typu najít argumenty typu S₁...Sᵥ tak, aby výraz:

M<S₁...Sᵥ>

je kompatibilní (§20.2) s D.

Na rozdíl od algoritmu odvození typu pro volání obecných metod v tomto případě existují pouze argumenty jako typy, ale žádné argumenty jako výrazy. Konkrétně neexistují žádné anonymní funkce, a proto není nutné provádět více fází odvozování.

Místo toho jsou všechny Xᵢ považovány za nepřipravenéa odvozování z každý typ argumentu UₑD odpovídající typ parametru TₑM. Pokud pro některý z Xᵢ nebyly nalezeny žádné hranice, odvození typu selže. V opačném případě jsou všechny Xᵢpřiřazeny k odpovídajícím Sᵢ, které jsou výsledkem odvozování typu.

12.6.3.15 Nalezení nejlepšího společného typu sady výrazů

V některých případech je potřeba odvodit běžný typ pro sadu výrazů. Zejména typy elementů implicitně zadaných polí a návratové typy anonymních funkcí s blokem těla jsou nalezeny tímto způsobem.

Nejlepší společný typ sady výrazů E₁...Eᵥ je určen následujícím způsobem:

  • Zavádí se nová nefixovaná proměnná typu X.
  • Pro každý výraz Eivýstupní typ odvození (§12.6.3.7) se provádí z něj do X.
  • X je pevná (§12.6.3.12), pokud je to možné, a výsledný typ je nejlepším běžným typem.
  • Jinak se odvození nezdaří.

Poznámka: Intuitivně toto odvozování odpovídá volání metody void M<X>(X x₁ ... X xᵥ) s Eᵢ jako argumenty a odvození X. koncová poznámka

12.6.4 Rozlišení přetížení

12.6.4.1 Obecné

Řešení přetížení je mechanismus vazby pro výběr nejlepší funkce k vyvolání na základě zadaného seznamu argumentů a sady kandidátských členů funkcí. Rozlišení přetížení vybere člena funkce, který se má vyvolat v následujících jedinečných kontextech v jazyce C#:

  • Vyvolání metody uvedené v rámci invocation_expression (§12.8.10).
  • Vyvolání konstruktoru instance pojmenovaného v object_creation_expression (§12.8.17.2).
  • Vyvolání přístupového objektu indexeru prostřednictvím element_access (§12.8.12).
  • Vyvolání předdefinovaného nebo uživatelem definovaného operátoru odkazovaného ve výrazu (§12.4.4 a §12.4.5).

Každý z těchto kontextů definuje sadu členů kandidátské funkce a seznam argumentů vlastním jedinečným způsobem. Například sada kandidátů pro vyvolání metody nezahrnuje metody označené jako překryté (§12.5), a metody v základní třídě nejsou kandidáty, pokud je v odvozené třídě použitelná jakákoli metoda (§12.8.10.2).

Po zjištění členů kandidátské funkce a seznamu argumentů je výběr nejlepšího člena funkce ve všech případech stejný:

  • Zaprvé je sada členů kandidátské funkce omezena na členy funkce, které se vztahují k danému seznamu argumentů (§12.6.4.2). Pokud je tato omezená sada prázdná, dojde k chybě v době kompilace.
  • Pak se nachází nejlepší člen funkce ze sady použitelných členů kandidátské funkce. Pokud sada obsahuje pouze jeden člen funkce, je tento člen funkce nejlepším členem funkce. V opačném případě je nejlepším členem funkce jeden člen funkce, který je lepší než všechny ostatní členy funkce s ohledem na daný seznam argumentů za předpokladu, že každý člen funkce je porovnán se všemi ostatními členy funkce pomocí pravidel v §12.6.4.3. Pokud neexistuje pouze jeden člen funkce, který je lepší než všechny ostatní členy funkce, je volání člena funkce nejednoznačné a dojde k chybě vazby.

Následující pododstavce definují přesné významy těchto termínů použitelný člen funkce a lepší člen funkce.

12.6.4.2 Použitelný člen funkce

Člen funkce se označuje jako použitelný člen funkce s ohledem na seznam argumentů A, pokud jsou splněny všechny následující podmínky:

  • Každý argument v A odpovídá parametru v deklaraci členu funkce, jak je popsáno v §12.6.2.2, maximálně jeden argument odpovídá každému parametru a jakýkoli parametr, ke kterému žádný argument neodpovídá, je volitelný parametr.
  • Pro každý argument v Aje režim předávání parametrů argumentu identický s režimem předávání parametrů odpovídajícího parametru a
    • pro parametr hodnoty nebo pole parametrů existuje implicitní převod (§10.2) z výrazu argumentu na typ odpovídajícího parametru, nebo
    • pro odkaz nebo výstupní parametr existuje převod identity mezi typem výrazu argumentu (pokud existuje) a typem odpovídajícího parametru nebo
    • pro vstupní parametr, pokud má odpovídající argument modifikátor in, existuje převod identity mezi typem výrazu argumentu (pokud existuje) a typem odpovídajícího parametru, nebo
    • pro vstupní parametr, pokud odpovídající argument vynechá in modifikátor, implicitní převod (§10.2) existuje z výrazu argumentu na typ odpovídajícího parametru.

Pro člen funkce, který obsahuje pole parametrů, pokud je člen funkce použitelný podle výše uvedených pravidel, je uvedeno, že je použitelné v jeho normálním formátu. Pokud člen funkce, který obsahuje pole parametrů, není použitelný v jeho normální podobě, může být místo toho člen funkce použitelný ve svém rozšířeném formuláři:

  • Rozšířená forma je vytvořena nahrazením pole parametrů v deklaraci člena funkce na nula nebo více hodnotových parametrů typu prvku pole parametrů tak, aby počet argumentů v seznamu argumentů A odpovídal celkovému počtu parametrů. Pokud A obsahuje méně argumentů než počet pevných parametrů v deklaraci členu funkce, nelze zvětšovanou formu členu funkce vytvořit, a proto ji nelze použít.
  • V opačném případě je rozbalený formulář použitelný, pokud pro každý argument v A, platí jedna z následujících hodnot:
    • režim předávání parametrů argumentu je shodný s režimem předávání parametrů odpovídajícího parametru a
      • Pro parametr s pevnou hodnotou nebo parametr vytvořený rozšířením existuje implicitní převod (§10.2) z výrazu argumentu na typ odpovídajícího parametru nebo
      • pro parametr předávaný odkazem je typ výrazu argumentu shodný s typem odpovídajícího parametru.
    • Způsob předávání parametrů argumentu je hodnota a způsob předávání pro odpovídající parametr je vstup. Existuje implicitní konverze (§10.2) z výrazu argumentu na typ odpovídajícího parametru.

Pokud implicitní převod typu argumentu na typ parametru vstupního parametru je dynamický implicitní převod (§10.2.10), výsledky nejsou definovány.

Příklad: Vzhledem k následujícím deklaracím a voláním metody:

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }
public static void M2(in int p1) { ... }
public static void Test()
{
    int i = 10; uint ui = 34U;

    M1(in i);   // M1(in int) is applicable
    M1(in ui);  // no exact type match, so M1(in int) is not applicable
    M1(i);      // M1(int) and M1(in int) are applicable
    M1(i + 5);  // M1(int) and M1(in int) are applicable
    M1(100u);   // no implicit conversion exists, so M1(int) is not applicable

    M2(in i);   // M2(in int) is applicable
    M2(i);      // M2(in int) is applicable
    M2(i + 5);  // M2(in int) is applicable
}

ukončit příklad

  • Statická metoda je použitelná pouze v případě, že skupina metod je výsledkem simple_name nebo member_access prostřednictvím typu.
  • Metoda instance je použitelná pouze v případě, že skupina metod je výsledkem simple_name, member_access prostřednictvím proměnné nebo hodnoty nebo base_access.
    • Pokud je skupina metod výsledkem simple_name, lze metodu instance použít pouze v případě, že this přístup je povolen §12.8.14.
  • Pokud je skupina metod výsledkem member_access, který může být prostřednictvím instance nebo typu, jak je popsáno v §12.8.7.2, lze použít jak metody instance, tak statické metody.
  • Obecná metoda, jejíž argumenty typu (explicitně zadané nebo odvozené) nesplňují všechna jejich omezení, není použitelná.
  • V kontextu převodu skupiny metod existuje převod identity (§10.2.2) nebo implicitní odkazový převod (§10.2.8) z metody návratového typu do návratového typu delegáta. V opačném případě není metoda kandidáta použitelná.

12.6.4.3 Lepší člen funkce

Pro účely určení lepšího člena funkce je vytvořen zjednodušený seznam argumentů A obsahující pouze samotné výrazy argumentů v pořadí, v jakém se zobrazují v původním seznamu argumentů, a vynechává jakékoli argumenty out nebo ref.

Seznamy parametrů pro každý z členů kandidátské funkce jsou sestaveny následujícím způsobem:

  • Rozbalený formulář se používá, pokud byl člen funkce použitelný pouze v rozšířeném formuláři.
  • Volitelné parametry bez odpovídajících argumentů se odeberou ze seznamu parametrů.
  • Odkazy a výstupní parametry se odeberou ze seznamu parametrů.
  • Parametry se přeuspořádají tak, aby se vyskytovaly na stejné pozici jako odpovídající argument v seznamu argumentů.

Při A seznamu argumentů se sadou výrazů argumentů {E₁, E₂, ..., Eᵥ} a dvěma platnými členy funkce Mᵥ a Mₓ s typy parametrů {P₁, P₂, ..., Pᵥ} a {Q₁, Q₂, ..., Qᵥ}, Mᵥ je definován jako lepší člen funkce než Mₓ pokud

  • pro každý argument není implicitní převod z Eᵥ na Qᵥ lepší než implicitní převod z Eᵥ na Pᵥa
  • pro alespoň jeden argument je převod z Eᵥ na Pᵥ lepší než převod z Eᵥ na Qᵥ.

Pokud jsou sekvence typu parametru {P₁, P₂, ..., Pᵥ} a {Q₁, Q₂, ..., Qᵥ} ekvivalentní (tj. každá Pᵢ má převod identity na odpovídající Qᵢ), použijí se následující pravidla pro řešení konfliktů, aby bylo možné určit lepšího člena funkce.

  • Pokud Mᵢ je ne generická metoda a Mₑ je obecná metoda, Mᵢ je lepší než Mₑ.
  • V opačném případě, pokud Mᵢ je použitelná v normální podobě a Mₑ má pole parametrů a je použitelné pouze v rozšířené podobě, pak Mᵢ je lepší než Mₑ.
  • V opačném případě, pokud obě metody mají pole parametrů a jsou použitelné pouze ve svých rozbalených formách, a pokud pole parametrů Mᵢ má méně prvků než pole parametrů Mₑ, pak Mᵢ je lepší než Mₑ.
  • V opačném případě, pokud Mᵥ má konkrétnější typy parametrů než Mₓ, Mᵥ je lepší než Mₓ. Nechť {R1, R2, ..., Rn} a {S1, S2, ..., Sn} představují nepotřebné a nevyexpandované typy parametrů Mᵥ a Mₓ. Mᵥjsou typy parametrů specifičtější než Mₓ, pokud u každého parametru Rx není méně specifický než Sx, a u alespoň jednoho parametru je Rx specifičtější než Sx:
    • Parametr typu je méně specifický než parametr bez typu.
    • Rekurzivně je konstruovaný typ konkrétnější než jiný konstruovaný typ (se stejným počtem argumentů typu), pokud je alespoň jeden argument typu konkrétnější a argument typu není menší než odpovídající argument typu v druhém.
    • Typ pole je konkrétnější než jiný typ pole (se stejným počtem dimenzí), pokud je typ prvku prvního konkrétnější než typ prvku druhého.
  • Jinak, pokud je jeden člen nezvednutým operátorem a druhý je povýšeným operátorem, nezvednutý operátor je lepší.
  • Pokud nebyl nalezen žádný člen funkce, aby byl lepší a všechny parametry Mᵥ mají odpovídající argument, zatímco výchozí argumenty musí být nahrazeny alespoň jedním volitelným parametrem v Mₓ, je lepší Mᵥ než Mₓ.
  • Pokud pro alespoň jeden parametr Mᵥ používá lepší volbu předávání parametrů (§12.6.4.4) než odpovídající parametr v Mₓ a žádný z parametrů v Mₓ nepoužívá lepší volbu předávání parametrů než Mᵥ, pak je Mᵥ lepší než Mₓ.
  • Jinak není žádný člen funkce lepší.

12.6.4.4 Lepší režim předávání parametrů

Je povoleno mít odpovídající parametry ve dvou přetížených metodách se liší pouze režimem předávání parametrů za předpokladu, že jeden z těchto dvou parametrů má režim předávání hodnot, jak je znázorněno níže:

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }

Vzhledem k int i = 10;podle §12.6.4.2mohou být volání M1(i) a M1(i + 5) výsledkem obou přetížení. V takových případech je režim předávání parametrů hodnotou lepší volbou způsobu předávání parametrů.

Poznámka: Pro argumenty vstupu, výstupu nebo předávání odkazů neexistuje žádná taková volba, protože tyto argumenty odpovídají pouze stejným režimům předávání parametrů. koncová poznámka

12.6.4.5 Lepší převod z výrazu

Vzhledem k implicitnímu převodu C₁, který převádí z výrazu E na typ T₁, a implicitnímu převodu C₂, který převádí z výrazu E na typ T₂, C₁ je lepším převodem než C₂, pokud platí jedna z následujících možností:

  • E přesně odpovídá T₁ a E přesně neodpovídá T₂ (§12.6.4.6)
  • E přesně odpovídá buď oběma, nebo žádnému z T₁ a T₂, a T₁ je lepším cílem konverze než T₂ (§12.6.4.7)
  • E je skupina metod (§12.2), T₁ je kompatibilní (§20.4) s jednou nejlepší metodou ze skupiny metod pro převod C₁a T₂ není kompatibilní s jedinou nejlepší metodou ze skupiny metod pro převod C₂

12.6.4.6 Přesně odpovídající výraz

Při zadání výrazu E a typu TEpřesně odpovídáT, pokud platí některá z následujících možností:

  • E má typ Sa převod identity existuje z S na T
  • E je anonymní funkce, T je buď typ delegáta D nebo typ stromu výrazů Expression<D> a platí jedna z následujících podmínek:
    • Odvozený návratový typ X existuje pro E v kontextu seznamu parametrů D (§12.6.3.12) a převod identity existuje z X na návratový typ D
    • E je async lambda bez návratové hodnoty a D má návratový typ, kterým je negenerický «TaskType»
    • E je nesynchronní a D má návratový typ Y, nebo E je asynchronní a D má návratový typ «TaskType»<Y>(§15.15.1), a platí jedno z následujícího:
      • Tělo E je výraz, který přesně odpovídající Y
      • Tělo E je blok, ve kterém každý návratový příkaz vrátí výraz, který přesně odpovídá Y

Lepší cílová hodnota převodu

Vzhledem k dvěma typům T₁ a T₂je T₁lepším cílovým převodu než T₂, pokud platí některá z následujících možností:

  • Implicitní převod z T₁ na T₂ existuje a neexistuje žádný implicitní převod z T₂ na T₁.
  • T₁ je «TaskType»<S₁>(§15.15.1), T₂ je «TaskType»<S₂>a S₁ je lepším cílem převodu než S₂
  • T₁ je «TaskType»<S₁>(§15.15.1), T₂ je «TaskType»<S₂>a T₁ je specializovanější než T₂
  • T₁ je S₁ nebo S₁?, kde S₁ je celočíselný typ a T₂ je S₂ nebo S₂?, kde S₂ je celočíselný typ bez znaménka. Konkrétně:
    • S₁ je sbyte a S₂ je byte, ushort, uintnebo ulong
    • S₁ je short a S₂ je ushort, uintnebo ulong
    • S₁ je int a S₂ je uintnebo ulong
    • S₁ je long a S₂ je ulong

12.6.4.8 Přetížení v obecných třídách

Poznámka: Zatímco podpisy deklarované musí být jedinečné (§8.6), je možné, že nahrazení argumentů typu vede k identickým podpisům. V takové situaci řešení přetížení vybere tu nejvíce specifickou možnost (§12.6.4.3) z původních podpisů (před nahrazením argumentů typu), pokud existuje; v opačném případě nahlásí chybu. koncová poznámka

Příklad: Následující příklady ukazují přetížení, která jsou platná a neplatná podle tohoto pravidla:

public interface I1<T> { ... }
public interface I2<T> { ... }

public abstract class G1<U>
{
    public abstract int F1(U u);           // Overload resolution for G<int>.F1
    public abstract int F1(int i);         // will pick non-generic

    public abstract void F2(I1<U> a);      // Valid overload
    public abstract void F2(I2<U> a);
}

abstract class G2<U,V>
{
    public abstract void F3(U u, V v);     // Valid, but overload resolution for
    public abstract void F3(V v, U u);     // G2<int,int>.F3 will fail

    public abstract void F4(U u, I1<V> v); // Valid, but overload resolution for
    public abstract void F4(I1<V> v, U u); // G2<I1<int>,int>.F4 will fail

    public abstract void F5(U u1, I1<V> v2);   // Valid overload
    public abstract void F5(V v1, U u2);

    public abstract void F6(ref U u);      // Valid overload
    public abstract void F6(out V v);
}

konec příkladu

12.6.5 Kontrola času kompilace dynamického vyvolání členů

I když se rozlišení přetížení dynamicky vázané operace provádí za běhu, je někdy možné v době kompilace zjistit seznam členů funkce, ze kterých bude vybráno přetížení:

  • Pro vyvolání delegáta (§12.8.10.4) je seznam jediným členem funkce se stejným seznamem parametrů jako u typu delegáta při vyvolání.
  • Pro vyvolání metody (§12.8.10.2) u typu nebo hodnoty, jejíž statický typ není dynamický, je sada přístupných metod ve skupině metod známa v době kompilace.
  • Výraz vytvoření objektu (§12.8.17.2) sada přístupných konstruktorů v typu je známa v době kompilace.
  • Pro přístup indexeru (§12.8.12.3) je sada přístupných indexerů v přijímači známa v době kompilace.

V těchto případech se provádí omezená kontrola během kompilace u každého člena v známé sadě členů funkce, aby bylo zjištěno, zda může být s jistotou známo, že nikdy nebude vyvolán během běhu programu. Pro každý člen funkce F upravený parametr a seznam argumentů se vytvoří:

  • Za prvé, pokud F je obecná metoda a byly zadány argumenty typu, pak jsou nahrazeny parametry typu v seznamu parametrů. Pokud však nebyly zadány argumenty typu, nedojde k žádné takové náhradě.
  • Je pak vyloučen jakýkoli parametr, jehož typ je otevřený (tj. obsahuje parametr typu; viz §8.4.3), spolu se svými odpovídajícími parametry.

Aby F prošel kontrolou, musí platit všechny následující podmínky:

  • Upravený seznam parametrů pro F se vztahuje na seznam upravených argumentů z hlediska §12.6.4.2.
  • Všechny vytvořené typy v seznamu upravených parametrů splňují svá omezení (§8.4.5).
  • Pokud byly parametry typu F v předchozím kroku nahrazeny, jsou jejich omezení splněna.
  • Je-li F statickou metodou, skupina metod nesmí pocházet z přístupu člena , jehož příjemce je při kompilaci znám jako proměnná nebo hodnota.
  • Je-li F metodou instance, skupina metod nesmí vzniknout z member_access, jejíchž příjemce je v době kompilace známý jako typ.

Pokud žádný kandidát tento test neprojde, dojde k chybě v době kompilace.

12.6.6 Vyvolání člena funkce

12.6.6.1 Obecné

Tato dílčí část popisuje proces, který probíhá při běhu programu k vyvolání konkrétního člena funkce. Předpokládá se, že proces vazby času již určil konkrétního člena, který má být vyvolán, pravděpodobně rozlišením přetížení na množinu funkčních členů.

Pro účely popisu procesu vyvolání jsou členy funkce rozdělené do dvou kategorií:

  • Statické funkční členy. Jedná se o statické metody, přístupové objekty statických vlastností a uživatelem definované operátory. Členové statické funkce jsou vždy ne virtualní.
  • Členy funkce instance Jedná se o metody instance, konstruktory instancí, přístupové objekty vlastností instance a přístupové objekty indexeru. Členové instančních funkcí jsou buď nevirtuální, nebo virtuální a jsou vždy voláni na konkrétní instanci. Instance je vypočítána výrazem instance a stává se přístupnou v rámci člena funkce jako this (§12.8.14). U konstruktoru instance se výraz instance použije jako nově přidělený objekt.

Zpracování běhu volání člena funkce se skládá z následujících kroků, kde M je členem funkce a pokud M je členem instance, E je výraz instance:

  • Pokud M je členem statické funkce:

    • Seznam argumentů je vyhodnocen tak, jak je popsáno v §12.6.2.
    • M je vyvolána.
  • V opačném případě, pokud je typ E hodnotového typu V, a M je deklarován nebo přepsán v V:

    • E se vyhodnotí. Pokud toto vyhodnocení způsobí výjimku, neprojdou žádné další kroky. Pro konstruktor instance se toto vyhodnocení skládá z přidělení úložiště (obvykle ze zásobníku spouštění) pro nový objekt. V tomto případě se E klasifikuje jako proměnná.
    • Pokud E není klasifikovaný jako proměnná nebo pokud V není typ struktury jen pro čtení (§16.2.2) a E je jedním z:

    pak se vytvoří dočasná místní proměnná typu Ea k této proměnné se přiřadí hodnota E. E je pak překlasifikováno jako odkaz na tuto dočasnou místní proměnnou. Dočasná proměnná je přístupná jako this v rámci M, ale ne jiným způsobem. Proto pouze v případě, že E lze zapsat, je pro volajícího možné sledovat změny, které M provede this.

    • Seznam argumentů je vyhodnocen tak, jak je popsáno v §12.6.2.
    • M je vyvolána. Proměnná, na kterou se odkazuje E, se stane proměnnou, na kterou se odkazuje this.
  • Jinak:

    • E se vyhodnotí. Pokud toto vyhodnocení způsobí výjimku, neprojdou žádné další kroky.
    • Seznam argumentů je vyhodnocen tak, jak je popsáno v §12.6.2.
    • Pokud je typ Evalue_type, provede se převod boxingu (§10.2.9) pro převod E na class_typea E se považuje za class_type v následujících krocích. Pokud je value_typeenum_type, pak je class_typeSystem.Enum;, jinak je System.ValueType.
    • Hodnota E je kontrolována tak, aby byla platná. Pokud je hodnota E null, vyvolá se System.NullReferenceException a neprojdou žádné další kroky.
    • Je určena implementace člena funkce, která se má vyvolat:
      • Pokud je typ vazby E rozhraní, člen funkce, který se má vyvolat, je implementace M poskytované typem běhu instance odkazované E. Tento člen funkce je určen použitím pravidel mapování rozhraní (§18.6.5) k určení implementace M poskytnutého typem běhu instance, na kterou odkazuje E.
      • V opačném případě, pokud M je členem virtuální funkce, člen funkce, který se má vyvolat, je implementace M poskytované typem běhu instance odkazované E. Tento člen funkce je určen použitím pravidel pro určení nejvíce odvozené implementace (§15.6.4) M s ohledem na typ běhu instance odkazované E.
      • V opačném případě je M ne virtuálním členem funkce a člen funkce, který se má vyvolat, je M sám.
    • Vyvolá se implementace člena funkce určená v kroku výše. Objekt, na který odkazuje E, se stane objektem, na který odkazuje toto.

Výsledkem vyvolání konstruktoru instance (§12.8.17.2) je vytvořená hodnota. Výsledkem vyvolání jakéhokoli jiného členu funkce je hodnota, pokud existuje, vrácená (§13.10.5) z jeho těla.

12.6.6.2 Vyvolání v krabicových instancích

Člen funkce implementovaný v value_type lze vyvolat prostřednictvím boxované instance tohoto value_type v následujících situacích:

  • Je-li člen funkce přepsanou metodou zděděnou z typu class_type a je vyvolán prostřednictvím výrazu instance tohoto class_type.

    Poznámka: class_type bude vždy jedním z System.Object, System.ValueType nebo System.Enumkoncová poznámka

  • Pokud je člen funkce implementací člena funkce rozhraní a je vyvolán prostřednictvím výrazu instance interface_type.
  • Když je člen funkce vyvolán prostřednictvím delegáta.

V těchto situacích se instance typu box považuje za obsahující proměnnou typu value_type, a tato proměnná se stane proměnnou, na kterou se odkazuje v rámci tohoto vyvolání členské funkce.

Poznámka: Konkrétně to znamená, že když je v boxované instanci vyvolán člen funkce, je možné, aby člen funkce upravil hodnotu obsaženou v boxované instanci. koncová poznámka

12.7 Dekonstrukce

Dekonstrukce je proces, při kterém se výraz změní na řazenou kolekci jednotlivých výrazů. Dekonstrukce se používá, když je cílem jednoduchého přiřazení n-tice, k získání hodnot pro přiřazení ke každému prvku této n-tice.

Výraz E je dekonstruovaný výrazu řazené kolekce členů s n prvky následujícím způsobem:

  • Pokud je E výraz n-tice s n prvky, výsledkem dekonstrukce je výraz E samotný.
  • Jinak, pokud má E typ n-tice (T1, ..., Tn) s n prvky, pak se E vyhodnotí do dočasné proměnné __va výsledkem dekonstrukce je výraz (__v.Item1, ..., __v.Itemn).
  • Jinak pokud se výraz E.Deconstruct(out var __v1, ..., out var __vn) přeloží v době kompilace na jedinečnou instanci nebo rozšiřující metodu, vyhodnotí se tento výraz a výsledkem dekonstrukce je výraz (__v1, ..., __vn). Taková metoda se označuje jako dekonstruktor.
  • V opačném případě nelze E dekonstruovat.

Tady __v a __v1, ..., __vn odkazují na jinak neviditelné a nepřístupné dočasné proměnné.

Poznámka: Výraz typu dynamic nelze dekonstruovat. koncová poznámka

12.8 Primární výrazy

12.8.1 Obecné

Primární výrazy zahrnují nejjednodušší formy výrazů.

primary_expression
    : primary_no_array_creation_expression
    | array_creation_expression
    ;

primary_no_array_creation_expression
    : literal
    | interpolated_string_expression
    | simple_name
    | parenthesized_expression
    | tuple_expression
    | member_access
    | null_conditional_member_access
    | invocation_expression
    | element_access
    | null_conditional_element_access
    | this_access
    | base_access
    | post_increment_expression
    | post_decrement_expression
    | null_forgiving_expression
    | object_creation_expression
    | delegate_creation_expression
    | anonymous_object_creation_expression
    | typeof_expression
    | sizeof_expression
    | checked_expression
    | unchecked_expression
    | default_value_expression
    | nameof_expression    
    | anonymous_method_expression
    | pointer_member_access     // unsafe code support
    | pointer_element_access    // unsafe code support
    | stackalloc_expression
    ;

Poznámka: Tato gramatická pravidla nejsou připravená na ANTLR, protože jsou součástí sady vzájemně rekurzivních pravidel (primary_expression, primary_no_array_creation_expression, member_access, invocation_expression, element_access, post_increment_expression, post_decrement_expression, null_forgiving_expression, pointer_member_access a pointer_element_access), které ANTLR nezpracuje. Standardní techniky lze použít k transformaci gramatiky, aby se odebrala vzájemná rekurze vlevo. To se neprovádí, protože ne všechny strategie analýzy ji vyžadují (např. analyzátor LALR by to neudělal) a tím by obfukovala strukturu a popis. koncová poznámka

pointer_member_access (§ 23.6.3) a pointer_element_access (§23.6.4) jsou k dispozici pouze v nebezpečném kódu (§23).

Primární výrazy se dělí na výrazy vytvářející polea primární výrazy nevytvářející pole. Tímto způsobem zacházení s array_creation_expression, namísto jeho uvedení společně s jinými formami jednoduchého výrazu, umožňuje gramatice zakázat potenciálně matoucí kód, jako například

object o = new int[3][1];

který by jinak byl interpretován jako

object o = (new int[3])[1];

12.8.2 Literály

primární_výraz, který se skládá z literálu (§6.4.5), je klasifikován jako hodnota.

12.8.3 Interpolované řetězcové výrazy

interpolated_string_expression se skládá z $, $@nebo @$, následovaný textem uvnitř " znaků. V citovaném textu je nula nebo více interpolací vymezených znaky { a }, z nichž každý zahrnuje výraz a volitelné specifikace formátování.

Interpolované řetězcové výrazy mají dvě formy; regular (interpolated_regular_string_expression) a doslovné (interpolated_verbatim_string_expression); které jsou lexicky podobné, ale liší se od dvou forem řetězcových literálů (§6.4.5.6).

interpolated_string_expression
    : interpolated_regular_string_expression
    | interpolated_verbatim_string_expression
    ;

// interpolated regular string expressions

interpolated_regular_string_expression
    : Interpolated_Regular_String_Start Interpolated_Regular_String_Mid?
      ('{' regular_interpolation '}' Interpolated_Regular_String_Mid?)*
      Interpolated_Regular_String_End
    ;

regular_interpolation
    : expression (',' interpolation_minimum_width)?
      Regular_Interpolation_Format?
    ;

interpolation_minimum_width
    : constant_expression
    ;

Interpolated_Regular_String_Start
    : '$"'
    ;

// the following three lexical rules are context sensitive, see details below

Interpolated_Regular_String_Mid
    : Interpolated_Regular_String_Element+
    ;

Regular_Interpolation_Format
    : ':' Interpolated_Regular_String_Element+
    ;

Interpolated_Regular_String_End
    : '"'
    ;

fragment Interpolated_Regular_String_Element
    : Interpolated_Regular_String_Character
    | Simple_Escape_Sequence
    | Hexadecimal_Escape_Sequence
    | Unicode_Escape_Sequence
    | Open_Brace_Escape_Sequence
    | Close_Brace_Escape_Sequence
    ;

fragment Interpolated_Regular_String_Character
    // Any character except " (U+0022), \\ (U+005C),
    // { (U+007B), } (U+007D), and New_Line_Character.
    : ~["\\{}\u000D\u000A\u0085\u2028\u2029]
    ;

// interpolated verbatim string expressions

interpolated_verbatim_string_expression
    : Interpolated_Verbatim_String_Start Interpolated_Verbatim_String_Mid?
      ('{' verbatim_interpolation '}' Interpolated_Verbatim_String_Mid?)*
      Interpolated_Verbatim_String_End
    ;

verbatim_interpolation
    : expression (',' interpolation_minimum_width)?
      Verbatim_Interpolation_Format?
    ;

Interpolated_Verbatim_String_Start
    : '$@"'
    | '@$"'
    ;

// the following three lexical rules are context sensitive, see details below

Interpolated_Verbatim_String_Mid
    : Interpolated_Verbatim_String_Element+
    ;

Verbatim_Interpolation_Format
    : ':' Interpolated_Verbatim_String_Element+
    ;

Interpolated_Verbatim_String_End
    : '"'
    ;

fragment Interpolated_Verbatim_String_Element
    : Interpolated_Verbatim_String_Character
    | Quote_Escape_Sequence
    | Open_Brace_Escape_Sequence
    | Close_Brace_Escape_Sequence
    ;

fragment Interpolated_Verbatim_String_Character
    : ~["{}]    // Any character except " (U+0022), { (U+007B) and } (U+007D)
    ;

// lexical fragments used by both regular and verbatim interpolated strings

fragment Open_Brace_Escape_Sequence
    : '{{'
    ;

fragment Close_Brace_Escape_Sequence
    : '}}'
    ;

Šest výše definovaných lexikálních pravidel je kontextově citlivých následujícím způsobem:

pravidlo kontextové požadavky
Interpolated_Regular_String_Mid Rozpoznáno pouze po Interpolated_Regular_String_Start, mezi jakýmikoli následujícími interpolacemi a před odpovídajícími Interpolated_Regular_String_End.
Pravidelný_Interpolace_Formát Rozpoznává se pouze v rámci regular_interpolation a pokud počáteční dvojtečka (:) není vnořená do žádného typu závorky (kulaté, složené/hranaté).
Interpolated_Regular_String_End Rozpoznává se pouze po Interpolated_Regular_String_Start a pouze v případě, že jakékoli mezisahující tokeny jsou buď Interpolated_Regular_String_Mids, nebo tokeny, které mohou být součástí regular_interpolations, včetně tokenů pro všechny interpolated_regular_string_expressionobsažené v těchto interpolacích.
Interpolated_Verbatim_String_MidVerbatim_Interpolation_FormatInterpolated_Verbatim_String_End Uznání těchto tří pravidel je stejné jako u odpovídajících pravidel výše s tím, že každé zmíněné pravidelné gramatické pravidlo je nahrazeno příslušným doslovným pravidlem.

Poznámka: Výše uvedená pravidla jsou kontextově citlivá, protože se jejich definice překrývají s jinými tokeny v jazyce. koncová poznámka

Poznámka: Výše uvedená gramatika není připravená pro ANTLR kvůli kontextově citlivým lexikálním pravidlům. Stejně jako u jiných generátorů lexerů ANTLR podporuje kontextová lexikální pravidla, například použití lexikálních režimů, ale jedná se o podrobnosti implementace, a proto není součástí této specifikace. koncová poznámka

interpolated_string_expression se klasifikuje jako hodnota. Pokud je okamžitě převeden na System.IFormattable nebo System.FormattableString s implicitní interpolovanou konverzí řetězce (§10.2.5), má interpolovaný řetězcový výraz tento typ. V opačném případě má typ string.

Poznámka: Rozdíly mezi možnými typy interpolated_string_expression mohou být určeny z dokumentace pro System.String (§C.2) a System.FormattableString (§C.3). koncová poznámka

Význam interpolace, regular_interpolation i verbatim_interpolation, je formátovat hodnotu výrazu jako string buď podle formátu určeného Regular_Interpolation_Format nebo Verbatim_Interpolation_Format, nebo podle výchozího formátu pro typ výrazu . Formátovaný řetězec je následně upraven pomocí interpolation_minimum_width, pokud existuje, aby vznikl konečný string, který bude interpolován do interpolated_string_expression.

Poznámka: Jak je určen výchozí formát pro typ, je podrobně popsán v dokumentaci pro System.String (§C.2) a System.FormattableString (§C.3). Popis standardních formátů, které jsou identické pro Regular_Interpolation_Format a Verbatim_Interpolation_Format, lze nalézt v dokumentaci pro System.IFormattable (§C.4) a v jiných typech standardní knihovny (§C). koncová poznámka

V interpolation_minimum_widthconstant_expression implicitní převod na int. Šířka pole by měla být absolutní hodnotou tohoto výrazu constant_expression a zarovnání by mělo být určeno znaménkem (kladným nebo záporným) hodnoty tohoto výrazu constant_expression:

  • Pokud je hodnota šířky pole menší nebo rovna délce formátovaného řetězce, formátovaný řetězec se nezmění.
  • V opačném případě je formátovaný řetězec vycpaný prázdnými znaky, aby jeho délka byla rovna šířce pole:
    • Pokud je zarovnání kladné, je formátovaný řetězec zarovnaný doprava tím, že předsadí odsazení,
    • V opačném případě je zarovnána doleva přidáním odsazení.

Celkový význam výrazu interpolated_string_expression, včetně výše uvedeného formátování a odsazení interpolací, je definován převodem výrazu na vyvolání metody: pokud je typ výrazu System.IFormattable nebo System.FormattableString, je tato metoda System.Runtime.CompilerServices.FormattableStringFactory.Create (§C.3), která vrátí hodnotu typu System.FormattableString; v opačném případě má typ string a metoda je string.Format (§C.2), která vrátí hodnotu typu string.

V obou případech se seznam argumentů volání skládá z řetězcového literálu formátu se specifikacemi formátu pro každou interpolaci a argumentem pro každý výraz odpovídající specifikacím formátu.

Řetězcový literál formátu je vytvořen následujícím způsobem, kde N je počet interpolací v interpolated_string_expression. Řetězcový literál formátu se skládá z pořadí:

  • Znaky Interpolated_Regular_String_Start nebo Interpolated_Verbatim_String_Start
  • Znaky Interpolated_Regular_String_Mid nebo Interpolated_Verbatim_String_Mid, pokud existují
  • Poté pokud N ≥ 1 pro každé číslo I od 0 do N-1:
    • Specifikace zástupného symbolu:
      • Levá složená závorka ({)
      • Desítkové znázornění I
      • Pokud má odpovídající regular_interpolation nebo verbatim_interpolationinterpolation_minimum_width, následuje čárka (,) a desítková reprezentace hodnoty constant_expression.
      • Znaky Regular_Interpolation_Format nebo Verbatim_Interpolation_Format, pokud existují, odpovídajících regular_interpolation nebo verbatim_interpolation
      • Pravá složená závorka (})
    • Znaky Interpolated_Regular_String_Mid nebo Interpolated_Verbatim_String_Mid, které bezprostředně následují po odpovídající interpolaci, pokud nějaká existuje
  • Nakonec se jedná o znaky Interpolated_Regular_String_End nebo Interpolated_Verbatim_String_End.

Následující argumenty jsou výrazy ze zadaných interpolací, pokud nějaké existují, v daném pořadí.

Pokud interpolated_string_expression obsahuje více interpolací, výrazy v těchto interpolacích se vyhodnocují v textovém pořadí zleva doprava.

Příklad :

Tento příklad používá následující funkce specifikace formátu:

  • specifikace formátu X, která formátuje celá čísla jako šestnáctková velká písmena,
  • výchozí formát pro string hodnotu je samotná hodnota.
  • kladné hodnoty zarovnání, které jsou zarovnané doprava v zadané minimální šířce pole,
  • záporné hodnoty zarovnání, které jsou zarovnané doleva v zadané minimální šířce pole,
  • definované konstanty pro interpolation_minimum_widtha
  • {{ a }} jsou formátovány jako { a }.

Daný:

string text = "red";
int number = 14;
const int width = -4;

Potom:

Interpolovaný řetězcový výraz ekvivalentní význam jako string hodnota
$"{text}" string.Format("{0}", text) "red"
$"{{text}}" string.Format("{{text}}) "{text}"
$"{ text , 4 }" string.Format("{0,4}", text) " red"
$"{ text , width }" string.Format("{0,-4}", text) "red "
$"{number:X}" string.Format("{0:X}", number) "E"
$"{text + '?'} {number % 3}" string.Format("{0} {1}", text + '?', number % 3) "red? 2"
$"{text + $"[{number}]"}" string.Format("{0}", text + string.Format("[{0}]", number)) "red[14]"
$"{(number==0?"Zero":"Non-zero")}" string.Format("{0}", (number==0?"Zero":"Non-zero")) "Non-zero"

konec příkladu

12.8.4 Jednoduché názvy

simple_name se skládá z identifikátoru, volitelně následovaného seznamem argumentů typu:

simple_name
    : identifier type_argument_list?
    ;

simple_name je buď I formuláře, nebo I<A₁, ..., Aₑ>formuláře, kde I je jeden identifikátor a I<A₁, ..., Aₑ> je volitelná type_argument_list. Pokud není zadán žádný type_argument_list, považujte e za nulu. simple_name se vyhodnotí a klasifikuje takto:

  • Je-li e nula a simple_name se zobrazí v prostoru deklarace místní proměnné (§7.3), který přímo obsahuje místní proměnnou, parametr nebo konstantu s názvem I, pak simple_name odkazuje na tuto místní proměnnou, parametr nebo konstantu a je klasifikována jako proměnná nebo hodnota.
  • Pokud je e nula a simple_name se zobrazí v deklaraci obecné metody, ale mimo atributy jeho method_declarationa pokud tato deklarace obsahuje parametr typu s názvem I, pak simple_name odkazuje na tento parametr typu.
  • Jinak platí, že pro každý typ instance T (§15.3.2), počínaje typem instance bezprostředně uzavřené deklarace typu a pokračováním s typem instance každé nadřazené třídy nebo deklarace struktury (pokud existuje):
    • Pokud je e nula a deklarace T obsahuje parametr typu s názvem I, simple_name odkazuje na tento parametr typu.
    • V opačném případě, pokud vyhledávání členů (§12.5) I v T s argumenty typu e vytvoří shodu:
      • Pokud T je typ instance bezprostředně ohraničující typ třídy nebo struktury a vyhledávání identifikuje jednu nebo více metod, je výsledkem skupina metod s přidruženým výrazem instance this. Pokud byl zadán seznam argumentů typu, používá se při volání obecné metody (§12.8.10.2).
      • Jinak pokud je T typem instance bezprostředně ohraničujícího typu třídy nebo struktury, pokud vyhledávání identifikuje člena instance a pokud se odkaz vyskytuje v rámci bloku konstruktoru instance, metody instance nebo objektu instance (§12.2.1), výsledek je stejný jako přístup člena (§12.8.7) formuláře this.I. K tomu může dojít pouze v případě, že e je nula.
      • V opačném případě je výsledek stejný jako přístup člena (§12.8.7) formuláře T.I nebo T.I<A₁, ..., Aₑ>.
  • V opačném případě, pro každý obor názvů N, počínaje oborem názvů, ve kterém se objeví simple_name, pokračujeme s každým nadřazeným oborem názvů (pokud existuje), a končíme globálním oborem názvů, následující kroky jsou vyhodnocovány, dokud není nalezena entita:
    • Pokud je e nula a I je název oboru názvů v N, pak:
      • Pokud je umístění, kde dojde k simple_name, uzavřeno deklarací oboru názvů pro N a deklarace oboru názvů obsahuje extern_alias_directive nebo using_alias_directive, které přidruží název I k oboru názvů nebo typu, pak je simple_name nejednoznačný a dojde k chybě v době kompilace.
      • V opačném případě simple_name odkazuje na obor názvů pojmenovaný I v N.
    • Pokud N obsahuje přístupný typ s názvem I a parametry typu e, pak:
      • Pokud je e nula a místo, kde se vyskytuje simple_name, je v rámci deklarace oboru názvů pro N, která obsahuje extern_alias_directive nebo using_alias_directive, jenž přidružuje název I k oboru názvů nebo typu, pak je simple_name nejednoznačný a dojde k chybě při kompilaci.
      • V opačném případě namespace_or_type_name odkazuje na typ vytvořený s danými argumenty typu.
    • V opačném případě, pokud je místo, kde se objeví simple_name, uzavřeno deklarací oboru názvů pro N:
      • Pokud je e nula a deklarace oboru názvů obsahuje extern_alias_directive nebo using_alias_directive, které přidruží název I k importovanému oboru názvů nebo typu, odkazuje simple_name na tento obor názvů nebo typ.
      • Jinak, pokud obory názvů importované prostřednictvím using_namespace_directivedeklarací oboru názvů obsahují přesně jeden typ s názvem I a s parametry typu e, pak simple_name odkazuje na tento typ vytvořený s uvedenými argumenty typu.
      • Jinak, pokud obory názvů importované using_namespace_directive, deklarace oboru názvů, obsahují více než jeden typ s názvem I a parametry typu e, pak je simple_name nejednoznačný a dojde k chybě při kompilaci.

    Poznámka: Tento krok přesně odpovídá odpovídajícímu kroku ve zpracování namespace_or_type_name (§7.8). koncová poznámka

  • V opačném případě, pokud je e nula a I je identifikátor _, simple_name je jednoduché zahození, což je forma výrazu deklarace (§12.17).
  • V opačném případě je simple_name nedefinovaný a dojde k chybě v době kompilace.

12.8.5 Výrazy v závorkách

výraz v závorkách se skládá z výrazu uzavřeného v závorkách.

parenthesized_expression
    : '(' expression ')'
    ;

Výraz "uzavřený_v závorkách" se vyhodnotí tím, že se nejprve vyhodnotí výraz uvnitř závorek. Pokud výraz v závorkách označuje obor názvů nebo typ, dojde k chybě v době kompilace. Jinak je výsledek parenthesized_expression výsledkem vyhodnocení obsaženého výrazu .

12.8.6 Výrazy n-tic

tuple_expression představuje řazenou kolekci členů a skládá se ze dvou nebo více čárkami oddělených a volitelně pojmenovaných výrazů uzavřených v závorkách. deconstruction_expression je zkratka pro řazenou kolekci členů obsahující implicitně napsané výrazy deklarace.

tuple_expression
    : '(' tuple_element (',' tuple_element)+ ')'
    | deconstruction_expression
    ;
    
tuple_element
    : (identifier ':')? expression
    ;
    
deconstruction_expression
    : 'var' deconstruction_tuple
    ;
    
deconstruction_tuple
    : '(' deconstruction_element (',' deconstruction_element)+ ')'
    ;

deconstruction_element
    : deconstruction_tuple
    | identifier
    ;

tuple_expression se řadí jako n-tice.

deconstruction_expressionvar (e1, ..., en) je zkratka pro tuple_expression(var e1, ..., var en) a řídí se stejným chováním. To platí rekurzivně u všech vnořených deconstruction_tuplev deconstruction_expression. Každý identifikátor vnořený do deconstruction_expression proto zavádí výraz deklarace (§12.17). V důsledku toho může k deconstruction_expression dojít pouze na levé straně jednoduchého přiřazení.

Výraz řazené kolekce členů má typ, pokud a pouze pokud každý z jeho výrazů prvků Ei má typ Ti. Typ musí být typ n-tice se stejnou početností jako n-tice ve výrazu, kde každý prvek je uveden následujícím způsobem:

  • Pokud má prvek tuplu v odpovídající pozici název Ni, pak prvek typu tuplu bude Ti Ni.
  • Jinak platí, že pokud je Ei formuláře Ni nebo E.Ni nebo E?.Ni, musí být prvek typu řazené kolekce členů Ti Ni, , pokud některé z následujících blokování:
    • Další prvek výrazu n-tice má název Ni, nebo
    • Další prvek n-tice beze jména má výraz ve tvaru Ni, E.Ni nebo E?.Ni, nebo
    • Ni má tvar ItemX, kde X je posloupnost desítkových číslic, které nejsou zahájeny0a které by mohly představovat pozici prvku n-tice, a X nepředstavuje pozici prvku.
  • V opačném případě musí být prvek typu n-tice Ti.

Výraz ntice se vyhodnocuje vyhodnocením výrazů jednotlivých prvků v pořadí zleva doprava.

Hodnotu n-tice lze získat z výrazu n-tice tak, že ji převedete na typ n-tice (§10.2.13), přeřazením jako hodnotu (§12.2.2) nebo jejím určením jako cíle dekonstrukčního přiřazení (§12.21.2).

Příklad :

(int i, string) t1 = (i: 1, "One");
(long l, string) t2 = (l: 2, null);
var t3 = (i: 3, "Three");          // (int i, string)
var t4 = (i: 4, null);             // Error: no type

V tomto příkladu jsou všechny čtyři výrazy n-tic platné. První dva, t1 a t2, nepoužívají typ návratové hodnoty n-tice, místo toho použijí implicitní převod n-tice. V případě t2se implicitní převod n-tice spoléhá na implicitní převody z 2 na long a z null na string. Třetí výraz n-tice má typ (int i, string), a lze proto překlasifikovat na hodnotu tohoto typu. Deklarace t4je naopak chyba: výraz n-tice nemá typ, protože jeho druhý prvek není typovaný.

if ((x, y).Equals((1, 2))) { ... };

Tento příklad ukazuje, že řazené kolekce členů můžou někdy vést k několika vrstvám závorek, zejména pokud je výraz řazené kolekce členů jediným argumentem vyvolání metody.

konec příkladu

12.8.7 Přístup členů

12.8.7.1 Obecné

member_access se skládá z primary_expression, predefined_typenebo qualified_alias_member, následované tokenem ".", následované identifikátorem, volitelně následované type_argument_list.

member_access
    : primary_expression '.' identifier type_argument_list?
    | predefined_type '.' identifier type_argument_list?
    | qualified_alias_member '.' identifier type_argument_list?
    ;

predefined_type
    : 'bool' | 'byte' | 'char' | 'decimal' | 'double' | 'float' | 'int'
    | 'long' | 'object' | 'sbyte' | 'short' | 'string' | 'uint' | 'ulong'
    | 'ushort'
    ;

Produkce qualified_alias_member je definována v §14.8.

member_access může mít podobu E.I nebo E.I<A₁, ..., Aₑ>, kde E je buď primary_expression, predefined_type, nebo qualified_alias_member,I je jediný identifikátor a <A₁, ..., Aₑ> je volitelný type_argument_list. Pokud není zadán žádný type_argument_list, považujte e za nulu.

přístup_člena s výrazem_primární typu dynamic je dynamicky vázán (§12.3.3). V tomto případě kompilátor klasifikuje přístup člena jako přístup k vlastnosti typu dynamic. Níže uvedená pravidla k určení významu přístupu member_access se použijí za běhu, přičemž se místo typu kompilace použije typ za běhu z primary_expression. Pokud tato klasifikace za běhu vede ke skupině metod, pak přístup k členu musí být primární výraz ve výrazu vyvolání .

member_access se vyhodnotí a klasifikuje takto:

  • Pokud e je nula a E je obor názvů a E obsahuje vnořený obor názvů s názvem I, výsledek je tento obor názvů.
  • Pokud je E obor názvů a E obsahuje přístupný typ s názvem I a parametry typu K, je výsledkem tento typ vytvořený s danými argumenty typu.
  • Pokud je E klasifikovaný jako typ, pokud E není parametr typu, a pokud vyhledávání členů (§12,5) I v E s parametry typu K vytvoří shodu, E.I se vyhodnotí a klasifikuje takto:

    Poznámka: Pokud je výsledkem takového vyhledávání člena skupina metod a K je nula, může skupina metod obsahovat metody s parametry typu. To umožňuje, aby takové metody byly považovány za odvozování argumentů typu. koncová poznámka

    • Pokud I identifikuje typ, je výsledkem tento typ vytvořený s libovolnými zadanými argumenty typu.
    • Pokud I identifikuje jednu nebo více metod, je výsledkem skupina metod bez přidruženého výrazu instance.
    • Pokud I identifikuje statickou vlastnost, je výsledkem přístup k vlastnosti bez přidruženého výrazu instance.
    • Pokud I identifikuje statické pole:
      • Pokud je pole jen pro čtení a odkaz se vyskytuje mimo statický konstruktor třídy nebo struktury, ve které je pole deklarováno, je výsledkem hodnota, a to hodnota statického pole I v E.
      • V opačném případě je výsledkem proměnná, konkrétně statické pole I v E.
    • Pokud I identifikuje statickou událost:
      • Pokud se odkaz vyskytuje v rámci třídy nebo struktury, ve které je událost deklarována, a událost byla deklarována bez event_accessor_declarations (§15.8.1), E.I je zpracována přesně tak, jako by I byla statická pole.
      • V opačném případě je výsledkem přístup k události bez přidruženého výrazu instance.
    • Pokud I identifikuje konstantu, je výsledkem hodnota, konkrétně hodnota této konstanty.
    • Pokud I identifikuje člen výčtu, je výsledkem hodnota, konkrétně hodnota tohoto člena výčtu.
    • V opačném případě je E.I neplatným odkazem na člena a dojde k chybě v době kompilace.
  • Pokud je E přístupem k vlastnosti, indexeru, proměnnou nebo hodnotou, jehož typ je T, a vyhledávání člena (§12.5) I v T s argumenty typu K vytvoří shodu, pak se E.I vyhodnotí a klasifikuje takto:
    • Nejprve, pokud je E vlastnost nebo přístup indexeru, získá se hodnota této vlastnosti nebo přístupu k indexeru (§12.2.2) a E se přetřídí jako hodnota.
    • Pokud I identifikuje jednu nebo více metod, je výsledkem skupina metod s přidruženým výrazem instance E.
    • Pokud I identifikuje vlastnost instance, je výsledkem přístup k vlastnosti s přidruženým výrazem instance E a přidruženým typem, který je typem vlastnosti. Je-li T typ třídy, je přidružený typ vybrán z první deklarace nebo přepsání vlastnosti, které se najde při zahájení od Ta vyhledávání v jejích základních třídách.
    • Pokud je Tclass_type a I identifikuje instanční pole daného class_type:
      • Pokud je hodnota Enull, vyvolá se System.NullReferenceException.
      • V opačném případě, pokud je pole jen pro čtení a odkaz nastane mimo konstruktor instance třídy, ve které je pole deklarováno, je výsledkem hodnota, a to hodnota pole I v objektu odkazovaném E.
      • Jinak je výsledkem proměnná, konkrétně pole I v objektu odkazovaném E.
    • Pokud je T struktura typu a I identifikuje pole instance této struktury typu :
      • Pokud E je hodnota, nebo pokud je pole jen pro čtení a odkaz nastane mimo konstruktor instance struktury, ve které je pole deklarováno, je výsledkem hodnota, a to hodnota pole I v instanci struktury dané E.
      • V opačném případě je výsledkem proměnná, konkrétně pole I v instanci struktury poskytnuté E.
    • Pokud I identifikuje událost instance:
      • Pokud se odkaz vyskytuje v rámci třídy nebo struktury, ve které je událost deklarována, a událost byla deklarována bez event_accessor_declarations (§15.8.1), a odkaz nenastane jako levá strana a += nebo -= operátor, pak se E.I zpracuje přesně tak, jako by I bylo pole instance.
      • V opačném případě je výsledkem přístup k události s přidruženým výrazem instance E.
  • V opačném případě je proveden pokus o zpracování E.I jako vyvolání rozšiřující metody (§12.8.10.3). Pokud se to nezdaří, E.I je neplatný odkaz na člena a dojde k chybě při vazbě času.

12.8.7.2 Stejné jednoduché názvy a názvy typů

V případě přístupu k členu ve formě E.I, pokud je E jediným identifikátorem a pokud význam E jako simple_name (§12.8.4) představuje konstantu, pole, vlastnost, místní proměnnou nebo parametr se stejným typem jako význam E jako type_name (§7.8.1), pak jsou povoleny oba možné významy E. Vyhledávání členů E.I není nikdy nejednoznačné, protože I musí být nutně členem typu E v obou případech. Jinými slovy pravidlo jednoduše povoluje přístup ke statickým členům a vnořeným typům E, kdy by jinak došlo k chybě kompilace.

Příklad :

struct Color
{
    public static readonly Color White = new Color(...);
    public static readonly Color Black = new Color(...);
    public Color Complement() => new Color(...);
}

class A
{
    public «Color» Color;              // Field Color of type Color

    void F()
    {
        Color = «Color».Black;         // Refers to Color.Black static member
        Color = Color.Complement();  // Invokes Complement() on Color field
    }

    static void G()
    {
        «Color» c = «Color».White;       // Refers to Color.White static member
    }
}

Pouze pro vysvětlující účely jsou v rámci třídy A ty výskyty identifikátoru Color, které odkazují na typ Color, odděleny pomocí «...», zatímco ty, které odkazují na pole Color, nejsou.

konec příkladu

12.8.8 Null Podmíněný přístup člena

null_conditional_member_access je podmíněná verze member_access (§12.8.7) a jedná se o chybu doby vazby, pokud je typ výsledku void. Pro podmíněný výraz s hodnotou null, kde může být typ výsledku void viz (§12.8.11).

null_conditional_member_access se skládá z primary_expression následovaného dvěma tokeny "?" a ".", následované identifikátorem s volitelným type_argument_list, následovaným nulou nebo více dependent_access, kterým lze předcházet null_forgiving_operator.

null_conditional_member_access
    : primary_expression '?' '.' identifier type_argument_list?
      (null_forgiving_operator? dependent_access)*
    ;
    
dependent_access
    : '.' identifier type_argument_list?    // member access
    | '[' argument_list ']'                 // element access
    | '(' argument_list? ')'                // invocation
    ;

null_conditional_projection_initializer
    : primary_expression '?' '.' identifier type_argument_list?
    ;

Výraz null_conditional_member_accessE má tvar P?.A. Význam E se určuje takto:

  • Pokud typ P je typ hodnoty nullable:

    Nechť je T typem P.Value.A.

    • Pokud T je parametr typu, o který není známo, že se jedná o odkazový typ nebo nenulový typ hodnoty, dojde k chybě v době kompilace.

    • Pokud je T nenulový typ hodnoty, typ E je T?a význam E je stejný jako význam:

      ((object)P == null) ? (T?)null : P.Value.A
      

      Kromě toho, že P se vyhodnotí pouze jednou.

    • V opačném případě je typ ETa význam E je stejný jako význam:

      ((object)P == null) ? (T)null : P.Value.A
      

      Kromě toho, že P se vyhodnotí pouze jednou.

  • Jinak:

    Nechte T být typem výrazu P.A.

    • Pokud T je parametr typu, o který není známo, že se jedná o odkazový typ nebo nenulový typ hodnoty, dojde k chybě v době kompilace.

    • Pokud je T nenulový typ hodnoty, typ E je T?a význam E je stejný jako význam:

      ((object)P == null) ? (T?)null : P.A
      

      Kromě toho, že P se vyhodnotí pouze jednou.

    • V opačném případě je typ ETa význam E je stejný jako význam:

      ((object)P == null) ? (T)null : P.A
      

      Kromě toho, že P se vyhodnotí pouze jednou.

Poznámka: Ve výrazu formuláře:

P?.A₀?.A₁

pokud se P vyhodnotí jako null, nevyhodnotí se ani A₀ ani A₁. Totéž platí, pokud je výraz posloupností operací null_conditional_member_access nebo null_conditional_element_access§12.8.13.

koncová poznámka

null_conditional_projection_initializer je omezením null_conditional_member_access a má stejnou sémantiku. Vyskytuje se pouze jako inicializátor projekce ve výrazu pro vytvoření anonymního objektu (§12.8.17.7).

12.8.9 Výrazy ignorující hodnotu Null

12.8.9.1 Obecné

Hodnota, typ, klasifikace výrazu s hodnotou null, typem, klasifikací (§12.2) a bezpečným kontextem (§16.4.12) je hodnota, typ, klasifikace a bezpečný kontext jeho primary_expression.

null_forgiving_expression
    : primary_expression null_forgiving_operator
    ;

null_forgiving_operator
    : '!'
    ;

Poznámka: Postfixové operátory pro toleranci hodnoty null a prefixové operátory logické negace (§12.9.4), ačkoliv jsou reprezentovány stejným lexikálním tokenem (!), jsou odlišné. Pouze ta poslední může být přepsána (§15.10), definice operátoru odpouštějícího hodnoty null je pevně daná. koncová poznámka

Jedná se o chybu v době kompilace při použití operátoru potlačení null více než jednou u stejného výrazu, bez ohledu na přítomnost závorek.

příklad: Toto jsou všechny neplatné:

var p = q!!;            // error: applying null_forgiving_operator more than once
var s = ( ( m(t) ! ) )! // error: null_forgiving_operator applied twice to m(t)

konec příkladu

Zbytek tohoto dílčího ustanovení a dalších souvisejících dílčích ustanovení jsou podmíněně normativní.

Kompilátor, který provádí statickou analýzu stavu null (§8.9.5) musí splňovat následující specifikaci.

Operátor null-forgiving je předkompilační pseudo-operace, která slouží jako vstup pro kompilátorovou statickou analýzu stavu null. Má dvě použití: k přepsání určení kompilátoru, že výraz může být nulla k přepsání varování kompilátoru souvisejícího s nulovou hodnotou.

Použití operátoru null-forgiving na výraz, pro který analýza statického stavu null kompilátoru nevyvolá žádná upozornění není chyba.

12.8.9.2 Přepsání určení "může být null"

Za určitých okolností může analýza statického stavu null kompilátoru určit, že výraz má nulový stav možná null a vydá diagnostické upozornění, pokud jiné informace indikují, že výraz nemůže mít hodnotu null. Použití operátoru null-forgiving na takový výraz informuje statickou analýzu stavu null kompilátoru, že stav null je v a není null, což brání diagnostickému upozornění a také může informovat jakoukoli probíhající analýzu.

příklad: Zvažte následující:

#nullable enable
public static void M()
{
    Person? p = Find("John");                  // returns Person?
    if (IsValid(p))
    {
       Console.WriteLine($"Found {p!.Name}");  // p can't be null
    }
}

public static bool IsValid(Person? person) =>
    person != null && person.Name != null;

Pokud IsValid vrátí true, p může být bezpečně dereferencováno pro přístup ke své Name vlastnosti a upozornění na "dereferencování potenciálně nulové hodnoty" lze potlačit pomocí !.

konec příkladu

Příklad: Operátor null-forgiving by měl být používán s opatrností, zvažte:

#nullable enable
int B(int? x)
{
    int y = (int)x!; // quash warning, throw at runtime if x is null
    return y;
}

Zde je operátor odpouštějící nuly použit na typ hodnoty a potlačuje všechna upozornění na x. Pokud je však xnull během běhu programu, dojde k výjimce, protože null nelze přetypovat na int.

konec příkladu

12.8.9.3 Přepsání dalších upozornění z analýzy nulových hodnot

Kromě přepsání určení, že může být null, jak je uvedeno výše, mohou existovat další okolnosti, kdy je žádoucí přepsat kompilátorovou analýzu stavu null, která stanoví, že výraz vyžaduje vydání jednoho nebo více upozornění. Použití operátoru null-forgiving na takový výraz vyžaduje, aby kompilátor nevydá žádné upozornění pro výraz. Kompilátor se může rozhodnout nevydávat upozornění a může také upravit svou další analýzu.

příklad: Zvažte následující:

#nullable enable
public static void Assign(out string? lv, string? rv) { lv = rv; }

public string M(string? t)
{
    string s;
    Assign(out s!, t ?? "«argument was null»");
    return s;
}

Typy parametrů metody Assign, lv & rv, jsou string?, přičemž lv je výstupní parametr, a provádí jednoduché přiřazení.

Metoda M předává proměnnou s, typu string, jako výstupní parametr Assign, a kompilátor vydává upozornění, protože s není nullovatelná proměnná. Vzhledem k tomu, že druhý argument Assignnemůže být null, operátor null-forgiving se používá k potlačení upozornění.

konec příkladu

Konec podmíněného normativního textu.

12.8.10 Vyvolání výrazů

12.8.10.1 Obecné

K vyvolání metody se používá invocation_expression.

invocation_expression
    : primary_expression '(' argument_list? ')'
    ;

primary_expression může být null_forgiving_expression, právě tehdy, když má delegate_type.

invocation_expression je dynamicky vázán (§12.3.3), pokud platí alespoň jedna z následujících podmínek:

  • primary_expression má typ kompilace dynamic.
  • Nejméně jeden argument v nepovinném seznamu argument_list má typ určený při kompilaci dynamic.

V tomto případě kompilátor klasifikuje invocation_expression jako hodnotu typu dynamic. Níže uvedená pravidla pro určení významu invocation_expression se pak použijí za běhu, přičemž se používá typ za běhu místo typu za doby kompilace pro ty z primary_expression a argumentů, které mají typ kompilace dynamic. Pokud primary_expression nemá typ kompilace dynamic, provede vyvolání metody omezenou kontrolu doby kompilace, jak je popsáno v §12.6.5.

primární výrazvýraz vyvolání musí být skupina metod nebo hodnota typu delegát . Je-li primární_výraz skupinou metod, výraz_volání je vyvoláním metody (§12.8.10.2). Je-li výraz primary_expression hodnotou typu delegate_type, pak je výraz invocation_expression vyvoláním delegáta (§12.8.10.4). Pokud primary_expression není skupinou metod ani hodnotou typu delegate_type, dojde k chybě v době vazby.

Nepovinný argument_list (§12.6.2) poskytuje hodnoty nebo proměnné odkazy na parametry metody.

Výsledek vyhodnocení invocation_expression je klasifikován takto:

  • Pokud invocation_expression vyvolá metodu vracející žádnou hodnotu (§15.6.1) nebo delegáta vracejícího žádnou hodnotu, výsledkem je nic. Výraz, který je klasifikován jako nic, je povolen pouze v kontextu "statement_expression" (§13.7) nebo jako tělo "lambda_expression" (§12.19). V opačném případě dojde k chybě časování vazby.
  • Jinak pokud invocation_expression vyvolá metodu s návratem podle odkazu (§15.6.1) nebo delegáta s návratem podle odkazu, výsledek je proměnná, jejíž typ je roven návratovému typu metody nebo delegáta. Pokud je vyvolána metoda instance a příjemce je typu třídy T, přidružený typ je vybrán z první deklarace nebo přepsání metody, které se najde při začátku s T a prohledávání jeho základních tříd.
  • V opačném případě invocation_expression vyvolá metodu s návratem hodnotou (§15.6.1) nebo delegáta s návratem hodnotou a výsledek je hodnota s odpovídajícím typem návratového typu metody nebo delegáta. Pokud je vyvolání metody instance a příjemce je typu třídy T, přidružený typ je vybrán z první deklarace nebo přepsání metody nalezen při zahájení T a vyhledávání v jeho základních třídách.

12.8.10.2 Vyvolání metody

Pro vyvolání metody musí být primary_expression v rámci invocation_expression skupinou metod. Skupina metod identifikuje jednu metodu, která se má vyvolat, nebo sadu přetížených metod, ze kterých zvolit konkrétní metodu, která se má vyvolat. V druhém případě je určení konkrétní metody vyvolání založeno na kontextu poskytovaném typy argumentů v argument_list.

Zpracování času vazby vyvolání metody tvaru M(A), kde M je skupina metod (pravděpodobně včetně type_argument_list) a A je volitelná argument_list, se skládá z následujících kroků:

  • Sada kandidátských metod pro vyvolání metody je vytvořena. Pro každou metodu F přidružené ke skupině metod M:
    • Pokud F není obecný, F je kandidátem v následujících případech:
      • M nemá žádný seznam argumentů typu a
      • F se vztahuje na A (§ 12.6.4.2).
    • Pokud je F obecný a M neobsahuje seznam argumentů typu, F je kandidátem v následujících případech:
      • Odvození typu (§12.6.3) je úspěšné, byl odvozen seznam argumentů typu pro volání a
      • Jakmile jsou odvozené argumenty typu nahrazeny parametry odpovídajícího typu metody, všechny konstruované typy v seznamu parametrů F splňují jejich omezení (§8.4.5) a seznam parametrů F se vztahuje na A (§12.6.4.2)
    • Pokud je F obecný a M obsahuje seznam argumentů typu, F je kandidátem v následujících případech:
      • F má stejný počet parametrů typu metody jako zadaný v seznamu argumentů typů a
      • Jakmile jsou argumenty typu nahrazeny odpovídajícími parametry typu metody, všechny konstruované typy v seznamu parametrů F splňují svá omezení (§8.4.5) a seznam parametrů F je použitelný pro A (§12.6.4.2).
  • Sada kandidátských metod je snížena tak, aby obsahovala pouze metody z nejvíce odvozených typů: Pro každou metodu C.F v sadě, kde C je typ, ve kterém je metoda F deklarována, všechny metody deklarované v základním typu C jsou ze sady odebrány. Navíc pokud C je jiný typ třídy než object, všechny metody deklarované v typu rozhraní jsou ze sady odebrány.

    Poznámka: Toto druhé pravidlo má účinek pouze v případě, že skupina metod byla výsledkem vyhledávání člena u parametru typu, který má efektivní základní třídu jinou než object a neprázdnou efektivní sadu rozhraní. koncová poznámka

  • Pokud je výsledná sada kandidátských metod prázdná, další zpracování podle následujících kroků je opuštěno a místo toho je proveden pokus o zpracování vyvolání metody rozšíření (§12.8.10.3). Pokud se to nezdaří, neexistují žádné použitelné metody a dojde k chybě při určování doby vazby.
  • Nejlepší způsob sady kandidátských metod je identifikován pomocí pravidel řešení přetížení §12.6.4. Pokud nelze identifikovat jednu nejlepší metodu, vyvolání metody je nejednoznačné a dojde k chybě při vazbě. Při provádění řešení přetížení se parametry obecné metody zohledňují po nahrazení odpovídajících parametrů typu metody argumenty typu (zadanými nebo odvozenými).

Jakmile je metoda vybrána a ověřena v době vazby výše uvedeným postupem, skutečné vyvolání za běhu se zpracuje podle pravidel volání člena funkce popsaného v §12.6.6.

Poznámka: Intuitivní účinek výše popsaných pravidel řešení je následující: Chcete-li vyhledat konkrétní metodu vyvolanou vyvoláním metody, začněte typem označeným vyvoláním metody a pokračujte v řetězu dědičnosti, dokud nenajdete alespoň jednu dostupnou deklaraci metody bez přepsání. Pak proveďte odvození typu a rozlišení přetížení u sady použitelných, přístupných, nepřekrývaných metod deklarovaných v daném typu, a takto vybranou metodu vyvolejte. Pokud nebyla nalezena žádná metoda, zkuste místo toho zpracovat vyvolání jako vyvolání metody rozšíření. koncová poznámka

12.8.10.3 Vyvolání metody rozšíření

Vyvolání metody (§12.6.6.2) jedné z forem

«expr» . «identifier» ( )  
«expr» . «identifier» ( «args» )  
«expr» . «identifier» < «typeargs» > ( )  
«expr» . «identifier» < «typeargs» > ( «args» )

Pokud normální zpracování vyvolání nenajde žádné použitelné metody, pokusí se zpracovat konstruktor jako vyvolání rozšiřující metody. Pokud má «výraz» nebo některý z «args» typ určený při kompilaci dynamic, metody rozšíření se neuplatní.

Cílem je najít nejlepší type_nameC, aby mohlo dojít k odpovídajícímu vyvolání statické metody.

C . «identifier» ( «expr» )  
C . «identifier» ( «expr» , «args» )  
C . «identifier» < «typeargs» > ( «expr» )  
C . «identifier» < «typeargs» > ( «expr» , «args» )

Metoda rozšíření Cᵢ.Mₑ je způsobilá, pokud:

  • Cᵢ je negenerická třída, která není vnořená.
  • Název Mₑ je identifikátor
  • Mₑ je přístupná a použitelná při použití na argumenty jako statická metoda, jak je znázorněno výše.
  • Implicitní převod identity, odkazu nebo boxování existuje z výrazu na typ prvního parametru Mₑ.

Hledání C pokračuje následujícím způsobem:

  • Počínaje nejbližší uzavřenou deklarací oboru názvů, pokračuje se každou další uzavřenou deklarací oboru názvů a končí u nadřazené kompilační jednotky. Postupně se pokoušíme najít kandidátskou sadu rozšiřujících metod.
    • Pokud daná jednotka oboru názvů nebo kompilační jednotka přímo obsahuje ne generické deklarace typu Cᵢ s způsobilými rozšiřujícími metodami Mₑ, pak sada těchto rozšiřujících metod je kandidátskou sadou.
    • Pokud obory názvů importované pomocí direktiv oboru názvů v daném oboru názvů nebo kompilační jednotce přímo obsahují deklarace ne generického typu Cᵢ s způsobilými rozšiřujícími metodami Mₑ, pak je sada těchto rozšiřujících metod kandidátskou sadou.
  • Pokud se v žádné uzavřené deklaraci oboru názvů nebo kompilační jednotce nenajde žádná kandidátská sada, dojde k chybě v době kompilace.
  • Jinak se rozlišení přetížení použije na kandidátskou sadu, jak je popsáno v §12.6.4. Pokud se nenajde žádná jedna nejlepší metoda, dojde k chybě v době kompilace.
  • C je typ, ve kterém je nejlepší metoda deklarována jako rozšiřující metoda.

Při použití C jako cíle se volání metody zpracuje jako volání statické metody (§12.6.6).

Poznámka: Na rozdíl od vyvolání metody instance není vyvolána žádná výjimka, pokud je výraz vyhodnocen jako odkaz null. Místo toho je hodnota null předána metodě rozšíření, stejně jako by byla předána prostřednictvím běžného volání statické metody. Je na implementaci metody rozšíření rozhodnout, jak reagovat na takové volání. koncová poznámka

Předchozí pravidla znamenají, že metody instance mají přednost před rozšiřujícími metodami, že metody rozšíření dostupné v deklaraci vnitřního oboru názvů mají přednost před rozšiřujícími metodami dostupnými v deklaracích vnějšího oboru názvů a že metody rozšíření deklarované přímo v oboru názvů mají přednost před rozšiřujícími metodami importovanými do stejného oboru názvů s použitím direktivy oboru názvů.

Příklad:

public static class E
{
    public static void F(this object obj, int i) { }
    public static void F(this object obj, string s) { }
}

class A { }

class B
{
    public void F(int i) { }
}

class C
{
    public void F(object obj) { }
}

class X
{
    static void Test(A a, B b, C c)
    {
        a.F(1);            // E.F(object, int)
        a.F("hello");      // E.F(object, string)
        b.F(1);            // B.F(int)
        b.F("hello");      // E.F(object, string)
        c.F(1);            // C.F(object)
        c.F("hello");      // C.F(object)
    }
}

V příkladu má metoda Bpřednost před první metodou rozšíření a metoda Cmá přednost před oběma metodami rozšíření.

public static class C
{
    public static void F(this int i) => Console.WriteLine($"C.F({i})");
    public static void G(this int i) => Console.WriteLine($"C.G({i})");
    public static void H(this int i) => Console.WriteLine($"C.H({i})");
}

namespace N1
{
    public static class D
    {
        public static void F(this int i) => Console.WriteLine($"D.F({i})");
        public static void G(this int i) => Console.WriteLine($"D.G({i})");
    }
}

namespace N2
{
    using N1;

    public static class E
    {
        public static void F(this int i) => Console.WriteLine($"E.F({i})");
    }

    class Test
    {
        static void Main(string[] args)
        {
            1.F();
            2.G();
            3.H();
        }
    }
}

Výstupem tohoto příkladu je:

E.F(1)
D.G(2)
C.H(3)

D.G má přednost před C.G, a E.F má přednost před D.F i C.F.

konec příkladu

12.8.10.4 Vyvolání delegáta

Pro vyvolání delegáta musí primary_expression výrazu invocation_expression být hodnotou typu delegate_type. Vzhledem k tomu, že delegate_type je členem funkce se stejným seznamem parametrů jako delegate_type, použitelnost delegate_type se určuje podle§12.6.4.2s ohledem na argument_list v kontextu výrazu invocation_expression.

Zpracování za běhu vyvolání delegáta ve tvaru D(A), kde D je výraz typu delegáta a A je volitelný seznam argumentů, se skládá z následujících kroků:

  • D se vyhodnotí. Pokud toto vyhodnocení způsobí výjimku, neprojdou žádné další kroky.
  • Vyhodnocuje se seznam argumentů A. Pokud toto vyhodnocení způsobí výjimku, neprojdou žádné další kroky.
  • Hodnota D je kontrolována tak, aby byla platná. Pokud je hodnota Dnull, vyvolá se System.NullReferenceException a nebude provedeno žádné další kroky.
  • V opačném případě je D odkazem na instanci delegáta. Vyvolání členů funkce (§12.6.6) se provádí na každém z volatelných entit v seznamu vyvolání delegáta. U volatelných entit, které se skládají z instance a metody instance, je instance volání instance obsažená v volatelné entitě.

Podrobnosti o více seznamech volání bez parametrů najdete v §20.6.

12.8.11 Výraz podmíněného vyvolání s hodnotou Null

null_conditional_invocation_expression je syntakticky buď null_conditional_member_access (§12.8.8) nebo null_conditional_element_access (§12.8.13), kde poslední dependent_access je výraz vyvolání (§12.8.10).

K null_conditional_invocation_expression dochází v kontextu statement_expression (§ 13.7), anonymous_function_body (§12.19.1) nebo method_body (§15.6.1).

Na rozdíl od syntakticky ekvivalentní null_conditional_member_access nebo null_conditional_element_accessmůže být null_conditional_invocation_expression klasifikována jako nic.

null_conditional_invocation_expression
    : null_conditional_member_access null_forgiving_operator? '(' argument_list? ')'
    | null_conditional_element_access null_forgiving_operator? '(' argument_list? ')'
    ;

Volitelný null_forgiving_operator může být zahrnut pouze tehdy a jen tehdy, pokud má null_conditional_member_access nebo null_conditional_element_accessdelegate_type.

Výraz null_conditional_invocation_expression je E formuláře P?A; pokud A je zbytek syntakticky ekvivalentní null_conditional_member_access nebo null_conditional_element_access, A proto začne s . nebo [. Nechť PA znamená spojení P a A.

Pokud E nastane jako statement_expression význam E je stejný jako význam příkazu :

if ((object)P != null) PA

s tím rozdílem, že P se vyhodnotí pouze jednou.

Pokud E nastane jako anonymous_function_body nebo method_body význam E závisí na jeho klasifikaci:

  • Pokud je E klasifikována jako nic, jeho význam je stejný jako význam bloku:

    { if ((object)P != null) PA; }
    

    s tím rozdílem, že P se vyhodnotí pouze jednou.

  • Jinak je význam E stejný jako význam bloku:

    { return E; }
    

    význam tohoto bloku závisí na tom, zda je syntakticky ekvivalentní null_conditional_member_access (§12.8.8) nebo null_conditional_element_access (§12.8.13).

12.8.12 Přístup k elementům

12.8.12.1 Obecné

element_access se skládá z primary_no_array_creation_expression, po kterém následuje token „[“, poté argument_list, a nakonec token „]“. argument_list se skládá z jednoho nebo více argumentů , oddělené čárkami.

element_access
    : primary_no_array_creation_expression '[' argument_list ']'
    ;

seznam_argumentů u přístupu_k_prvku nesmí obsahovat argumenty out ani ref.

element_access je dynamicky svázaný (§12.3.3) v případě, že platí alespoň jeden z následujících:

  • primary_no_array_creation_expression má typ pro dobu kompilace dynamic.
  • Nejméně jeden výraz argument_list má typ kompilace dynamic a primary_no_array_creation_expression nemá typ pole.

V tomto případě kompilátor klasifikuje element_access jako hodnotu typu dynamic. Níže uvedená pravidla pro určení významu element_access se aplikují za běhu programu, přičemž se místo typu doby kompilace těchto výrazů primary_no_array_creation_expression a argument_list, které mají typ kompilace dynamic, použije typ za běhu. Pokud primary_no_array_creation_expression nemá typ kompilace dynamic, projde přístup k prvku omezenou kontrolou doby kompilace, jak je popsáno v §12.6.5.

Je-li primary_no_array_creation_expressionelement_access hodnotou typu pole, pak element_access představuje přístup k prvku pole (§12.8.12.2). V opačném případě musí být primary_no_array_creation_expression proměnnou nebo hodnotou třídy, struktury nebo typu rozhraní, který má jeden nebo více členů indexeru, v takovém případě je element_access přístup indexeru (§12.8.12.3).

12.8.12.2 Přístup k poli

Pro přístup k poli musí být výraz primary_no_array_creation_expression u přístupu k prvkům element_access hodnotou typu array_type. Kromě toho argument_list přístupu k poli nesmí obsahovat pojmenované argumenty. Počet výrazů v argument_list musí být stejný jako pořadí array_typea každý výraz musí být typu int, uint, longnebo ulong, nebo musí být implicitně konvertibilní na jeden nebo více těchto typů.

Výsledkem vyhodnocení přístupu k poli je proměnná typu prvku pole, konkrétně prvek pole vybraný hodnotami výrazů v argument_list.

Zpracování přístupu k poli ve tvaru P[A], kde P je primary_no_array_creation_expression typu array_type a A je argument_list, se skládá z následujících kroků:

  • P se vyhodnotí. Pokud toto vyhodnocení způsobí výjimku, neprojdou žádné další kroky.
  • Výrazy indexu argument_list se vyhodnocují v pořadí zleva doprava. Při vyhodnocování každého výrazu indexu se provádí implicitní převod (§10,2) na jeden z následujících typů: int, uint, long, ulong. První typ v tomto seznamu, pro který existuje implicitní převod, je vybrán. Pokud je například výraz indexu typu short pak se provede implicitní převod na int, protože je možné implicitní převody z short na int a z short na long. Pokud vyhodnocení výrazu indexu nebo následné implicitní převody způsobí výjimku, nevyhodnotí se žádné další výrazy indexu a neprojdou žádné další kroky.
  • Hodnota P je kontrolována tak, aby byla platná. Pokud je hodnota Pnull, vyvolá se System.NullReferenceException a nejsou provedeny žádné další kroky.
  • Hodnota každého výrazu v argument_list je kontrolována na základě skutečných hranic každé dimenze instance pole, na kterou odkazuje P. Pokud je jedna nebo více hodnot mimo rozsah, vyvolá se System.IndexOutOfRangeException a neprojdou žádné další kroky.
  • Umístění prvku pole zadaného výrazy indexu se vypočítá a toto umístění se stane výsledkem přístupu k poli.

12.8.12.3 Přístup k indexeru

Pro přístup k indexeru musí být primary_no_array_creation_expressionelement_access proměnnou nebo hodnotou třídy, struktury nebo typu rozhraní a tento typ musí implementovat jeden nebo více indexerů, které se vztahují k argument_listelement_access.

Zpracování přístupu k indexeru P[A]ve formě, kde P je primary_no_array_creation_expression typu třídy, struktury nebo rozhraní T, a A je argument_list, se skládá z následujících kroků:

  • Sada indexerů, kterou poskytuje T, byla vytvořena. Sada se skládá ze všech indexerů deklarovaných v T nebo v základním typu T, které nejsou deklaracemi přepsání a jsou přístupné v aktuálním kontextu (§7.5).
  • Sada je omezena na ty indexery, které jsou použitelné a nejsou skryty jinými indexery. Následující pravidla se použijí pro každý indexer S.I v sadě, kde S je typ, ve kterém je deklarován indexer I:
    • Pokud se I nevztahuje na A (§12.6.4.2), I se ze sady odebere.
    • Je-li I použitelná pro A (§12.6.4.2), všechny indexery deklarované v základním typu S jsou ze sady odebrány.
    • Je-li I použitelná pro A (§ 12.6.4.2) a S je typ třídy jiný než object, všechny indexery deklarované v rozhraní jsou ze sady odebrány.
  • Pokud je výsledná sada kandidátských indexerů prázdná, neexistují žádné použitelné indexery a dojde k chybě vazby.
  • Nejlepší indexer sady kandidátských indexerů je identifikován pomocí pravidel řešení přetížení §12.6.4. Pokud nelze identifikovat jediný optimální indexer, je přístup k indexeru nejednoznačný a dojde k chybě času vazby.
  • Výrazy indexu argument_list se vyhodnocují v pořadí zleva doprava. Výraz získaný zpracováním přístupu k indexeru je klasifikován jako přístup indexeru. Výraz přístupu indexeru odkazuje na indexer určený v kroku výše a má přidružený výraz instance P a přidružený seznam argumentů Aa přidružený typ, který je typem indexeru. Pokud T je typ třídy, je přidružený typ vybrán z první deklarace nebo přepsání indexeru nalezen při spuštění T a vyhledávání v jeho základních třídách.

V závislosti na kontextu, ve kterém se používá, způsobí přístup indexeru vyvolání přístupového objektu get nebo sady přístupového objektu indexeru. Pokud je přístup k indexeru cílem přiřazení, je vyvolán objekt set pro přiřazení nové hodnoty (§12.21.2). Ve všech ostatních případech je vyvolán get accessor k získání aktuální hodnoty (§12.2.2).

12.8.13 Přístup k podmíněnému elementu null

null_conditional_element_access se skládá z primary_no_array_creation_expression následovaných dvěma tokeny "?" a "[", následované argument_list, následovaným tokenem "]" následovaným nulou nebo více dependent_accesses, které může předcházet null_forgiving_operator.

null_conditional_element_access
    : primary_no_array_creation_expression '?' '[' argument_list ']'
      (null_forgiving_operator? dependent_access)*
    ;

null_conditional_element_access je podmíněná verze element_access (§12.8.12) a jedná se o chybu doby vazby, pokud je typ výsledku void. Pro podmíněný výraz s hodnotou null, kde může být typ výsledku void viz (§12.8.11).

Výraz null_conditional_element_accessE má formu P?[A]B; kde jsou dependent_accesses B, pokud existují. Význam E se určuje takto:

  • Pokud typ P je typ hodnoty nullable:

    Nechte T být typem výrazu P.Value[A]B.

    • Pokud T je parametr typu, o který není známo, že se jedná o odkazový typ nebo nenulový typ hodnoty, dojde k chybě v době kompilace.

    • Pokud je T nenulový typ hodnoty, typ E je T?a význam E je stejný jako význam:

      ((object)P == null) ? (T?)null : P.Value[A]B
      

      Kromě toho, že P se vyhodnotí pouze jednou.

    • V opačném případě je typ ETa význam E je stejný jako význam:

      ((object)P == null) ? null : P.Value[A]B
      

      Kromě toho, že P se vyhodnotí pouze jednou.

  • Jinak:

    Nechte T být typem výrazu P[A]B.

    • Pokud T je parametr typu, o který není známo, že se jedná o odkazový typ nebo nenulový typ hodnoty, dojde k chybě v době kompilace.

    • Pokud je T nenulový typ hodnoty, typ E je T?a význam E je stejný jako význam:

      ((object)P == null) ? (T?)null : P[A]B
      

      Kromě toho, že P se vyhodnotí pouze jednou.

    • V opačném případě je typ ETa význam E je stejný jako význam:

      ((object)P == null) ? null : P[A]B
      

      Kromě toho, že P se vyhodnotí pouze jednou.

Poznámka: Ve výrazu formuláře:

P?[A₀]?[A₁]

pokud se P vyhodnotí jako null, nevyhodnotí se ani A₀ ani A₁. Totéž platí, pokud je výraz sekvencí operací null_conditional_element_access nebo null_conditional_member_access§12.8.8.

koncová poznámka

12.8.14 Tento přístup

this_access se skládá z klíčového slova this.

this_access
    : 'this'
    ;

this_access je povolena pouze v bloku konstruktoru instance, metody instance, přístupového objektu instance (§12.2.1) nebo finalizátoru. Má jeden z následujících významů:

  • Pokud se this používá v primary_expression v konstruktoru instance třídy, je klasifikovaný jako hodnota. Typ hodnoty je typ instance (§15.3.2) třídy, ve které dochází k použití, a hodnota je odkazem na objekt, který je vytvořen.
  • Pokud je this použito v rámci primary_expression v metodě instance nebo jako přístupový objekt instance třídy, je klasifikováno jako hodnota. Typ hodnoty je typ instance (§15.3.2) třídy, ve které dochází k použití, a hodnota je odkazem na objekt, pro který byla vyvolána metoda nebo příslušenství.
  • Pokud se this používá v primárním výrazu v konstruktoru instance struktury, je klasifikováno jako proměnná. Typ proměnné je typ instance (§15.3.2) struktury, ve které dochází k použití, a proměnná představuje strukturu, která je vytvořena.
    • Pokud deklarace konstruktoru nemá žádný inicializátor konstruktoru, this proměnná se chová přesně stejně jako výstupní parametr typu struktury. Konkrétně to znamená, že proměnná musí být rozhodně přiřazena v každé cestě provádění konstruktoru instance.
    • V opačném případě se proměnná this chová přesně stejně jako ref parametr typu struktury. Konkrétně to znamená, že proměnná se považuje za původně přiřazenou.
  • Pokud se this používá v primárním výrazu v rámci instanční metody nebo instančního přístupového objektu struktury, je klasifikován jako proměnná. Typ proměnné je typ instance (§15.3.2) struktury, ve které dochází k použití.
    • Pokud metoda nebo příslušenství není iterátor (§15.14) nebo asynchronní funkce (§15.15), představuje proměnná this strukturu, pro kterou byla metoda nebo příslušenství vyvolána.
      • Pokud je struktura readonly struct, this proměnná se chová úplně stejně jako vstupní parametr typu struktury.
      • Jinak se proměnná this chová přesně stejně jako parametr ref typu struktury.
    • Pokud je metoda nebo přístupová funkce iterátor nebo asynchronní funkce, představuje proměnná thiskopírování struktury, pro kterou byla vyvolána metoda nebo příslušenství, a chová se přesně stejně jako hodnota parametru typu struktury.

Použití this v primary_expression v jiném kontextu, než je uvedeno výše, je chyba v době kompilace. Konkrétně není možné odkazovat na this ve statické metodě, přístupovém objektu statické vlastnosti nebo při inicializaci proměnné při deklaraci pole.

12.8.15 Základní přístup

base_access se skládá ze základu klíčových slov následovaného tokenem "." a identifikátorem a volitelným type_argument_list nebo argument_list uzavřenými v hranatých závorkách:

base_access
    : 'base' '.' identifier type_argument_list?
    | 'base' '[' argument_list ']'
    ;

base_access slouží k přístupu k členům základní třídy, které jsou skryty podobnými pojmenovanými členy v aktuální třídě nebo struktuře. base_access je povoleno pouze v těle konstruktoru instance, instanční metody, instančního přístupového objektu (§12.2.1) nebo ve finalizátoru. Pokud base.I nastane ve třídě nebo struktuře, označím člen základní třídy této třídy nebo struktury. Stejně tak platí, že pokud base[E] nastane ve třídě, musí příslušný indexer existovat v základní třídě.

V době vazby se base_access výrazy formuláře base.I a base[E] vyhodnotí přesně tak, jako kdyby byly zapsány ((B)this).I a ((B)this)[E], kde B je základní třída třídy nebo struktury, ve které se konstruktor vyskytuje. Proto base.I a base[E] odpovídají this.I a this[E], s výjimkou this se zobrazuje jako instance základní třídy.

Pokud base_access odkazuje na člena virtuální funkce (metoda, vlastnost nebo indexer), je změněno určení, který člen funkce se má vyvolat za běhu (§12.6.6). Člen funkce, který je vyvolán, je určen nalezením nejodvozenější implementace (§15.6.4) člena funkce s ohledem na B (místo vzhledem k běhovému typu this, jak by bylo obvyklé při přístupu bez základového typu). Proto lze během přepsání virtuálního funkčního člena použít base_access k vyvolání zděděné implementace tohoto člena. Pokud je člen funkce odkazovaný base_access abstraktní, nastane chyba během vazby.

Poznámka: Na rozdíl od thisnení base výraz sám o sobě. Je to klíčové slovo pouze v kontextu base_access nebo constructor_initializer (§15.11.2). koncová poznámka

12.8.16 Operátory přírůstku a dekrementace

post_increment_expression
    : primary_expression '++'
    ;

post_decrement_expression
    : primary_expression '--'
    ;

Operand postfixové operace přiřazení nebo dekrementace může být výraz klasifikovaný jako proměnná, přístup k vlastnosti, nebo přístup indexeru. Výsledkem operace je hodnota stejného typu jako operand.

Pokud primary_expressiondynamic typ kompilace, je operátor dynamicky vázán (§12.3.3), post_increment_expression nebo post_decrement_expression má typ kompilace dynamic a následující pravidla se použijí za běhu pomocí typu běhu primary_expression.

Pokud je operandem postfixní inkrementace nebo dekrementace přístup k vlastnosti nebo indexeru, vlastnost nebo indexer musí mít jak get, tak set přístupový prvek. Pokud tomu tak není, dojde k chybě při určování doby vazby.

Rozlišení přetížení unárního operátoru (§12.4.4) se použije k výběru konkrétní implementace operátoru. Předdefinované operátory ++ a -- existují pro následující typy: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimala libovolný typ výčtu. Předdefinované operátory ++ vrátí hodnotu vytvořenou přidáním 1 do operandu a předdefinované operátory -- vrátí hodnotu vytvořenou odečtením 1 z operandu. Pokud je výsledek tohoto sčítání nebo odčítání mimo rozsah typu výsledku a typ výsledku je celočíselný typ nebo výčtový typ, vyvolá se System.OverflowException.

Existuje implicitní převod z návratového typu vybraného unárního operátoru na typ primary_expression, jinak dojde k chybě v době kompilace.

Zpracování běhu operace přírůstku nebo dekrementace formuláře x++ nebo x-- se skládá z následujících kroků:

  • Pokud se x klasifikuje jako proměnná:
    • x se vyhodnotí tak, aby vznikla proměnná.
    • Hodnota x se uloží.
    • Uložená hodnota x je převedena na typ operandu vybraného operátoru a operátor je vyvolán s touto hodnotou jako argument.
    • Hodnota vrácená operátorem je převedena na typ x a uložena na místo určené dřívějším vyhodnocením x.
    • Uložená hodnota x se stane výsledkem operace.
  • Pokud je x klasifikován jako přístup k vlastnosti nebo indexeru:
    • Výraz instance (pokud x není static) a seznam argumentů (pokud x je přístup k indexeru) přidružený k x se vyhodnotí a výsledky se použijí v následných vyvolání get a set.
    • Vyvolá se přístupový objekt get x a vrácená hodnota se uloží.
    • Uložená hodnota x je převedena na typ operandu vybraného operátoru a operátor je vyvolán s touto hodnotou jako argument.
    • Hodnota vrácená operátorem je převedena na typ x a nastavovací přístupová funkce x je vyvolána s touto hodnotou jako argument pro hodnotu.
    • Uložená hodnota x se stane výsledkem operace.

Operátory ++ a -- také podporují zápis předpon (§12.9.6). Výsledkem x++ nebo x-- je hodnota xpřed operace, zatímco výsledek ++x nebo --x je hodnota xpo operace. V obou případech má x stejnou hodnotu po operaci.

Implementaci operátoru ++ nebo operátoru -- lze vyvolat pomocí postfixové nebo prefixové notace. Pro dva různé zápisy není možné mít samostatné implementace operátorů.

12.8.17 Nový operátor

12.8.17.1 Obecné

Operátor new slouží k vytvoření nových instancí typů.

Existují tři formy nových výrazů:

  • Výrazy pro vytváření objektů a výrazy pro vytváření anonymních objektů se používají k vytváření nových instancí typů tříd a hodnotových typů.
  • Výrazy pro vytváření polí se používají k vytváření nových instancí typů polí.
  • Výrazy vytváření delegátů se používají k získání instancí typů delegátů.

Operátor new znamená vytvoření instance typu, ale nemusí nutně znamenat přidělení paměti. Konkrétně instance hodnotových typů nevyžadují žádnou další paměť nad rámec proměnných, ve kterých se nacházejí, a při použití new k vytváření instancí typů hodnot nedojde k žádným přidělením.

Poznámka: Výrazy vytváření delegátů nevytvoří vždy nové instance. Při zpracování výrazu stejným způsobem jako převod skupiny metod (§10.8) nebo anonymní převod funkce (§10.7) může dojít k opakovanému použití existující instance delegáta. koncová poznámka

12.8.17.2 Výrazy vytváření objektů

object_creation_expression slouží k vytvoření nové instance class_type nebo value_type.

object_creation_expression
    : 'new' type '(' argument_list? ')' object_or_collection_initializer?
    | 'new' type object_or_collection_initializer
    ;

object_or_collection_initializer
    : object_initializer
    | collection_initializer
    ;

Typ object_creation_expression musí být class_type, value_typenebo type_parameter. Typ nemůže být tuple_type ani abstraktním ani statickým class_type.

Nepovinný argument_list (§ 12.6.2) je povolen pouze v případě, že typ je class_type nebo struct_type.

Výraz pro vytvoření objektu může vynechat seznam argumentů konstruktoru a uzavřít závorky, pokud obsahuje inicializátor objektů nebo inicializátor kolekce. Vynechání seznamu argumentů konstruktoru a uzavření závorek je ekvivalentní zadání prázdného seznamu argumentů.

Zpracování výrazu vytvoření objektu, který obsahuje inicializátor objektu nebo inicializátor kolekce, se skládá z prvního zpracování konstruktoru instance a následné zpracování inicializace člena nebo prvku určené inicializátorem objektu (§12.8.17.17.3) nebo inicializátoru kolekce (§12.8.17.4).

Pokud některý z argumentů ve volitelném argument_list má typ při kompilaci dynamic, je object_creation_expression dynamicky vázán (§12.3.3) a následující pravidla se použijí za běhu pomocí běhového typu těchto argumentů z argument_list, které mají typ při kompilaci dynamic. Vytvoření objektu však prochází omezenou kontrolou doby kompilace, jak je popsáno v §12.6.5.

Zpracování vazby výrazu pro vytvoření objektu ve formátu nový T(A), kde T je třídního typunebo hodnotového typua A je volitelný seznam argumentů, sestává z následujících kroků:

  • Pokud je Tvalue_type a A neexistuje:
    • object_creation_expression je vyvolání výchozího konstruktoru. Výsledkem object_creation_expression je hodnota typu T, konkrétně výchozí hodnota pro T definovaná v §8.3.3.
  • Jinak, pokud je T typu type_parameter a A není přítomen:
    • Není-li pro Tzadáno žádné omezení typu hodnoty, ani omezení konstruktoru (§15.2.5), dojde k chybě při době vazby.
    • Výsledkem object_creation_expression je hodnota typu runtime, ke kterému byl parametr typu vázán, a to konkrétně výsledek vyvolání výchozího konstruktoru tohoto typu. Typ běhu může být referenčním typem nebo typem hodnoty.
  • Jinak, pokud je T typu class_type nebo typu struct_type:
    • Pokud T je abstraktní nebo statický class_type, dojde k chybě v době kompilace.
    • Konstruktor instance, který se má vyvolat, je určen pomocí pravidel rozlišení přetížení §12.6.4. Sada konstruktorů instance kandidáta se skládá ze všech přístupných konstruktorů instancí deklarovaných v T, které se vztahují k A (§12.6.4.2). Pokud je sada kandidátních konstruktorů instance prázdná nebo pokud nelze identifikovat jeden nejlepší konstruktor instance, dojde k chybě při vazbě.
    • Výsledkem object_creation_expression je hodnota typu T, konkrétně hodnota vytvořená vyvoláním konstruktoru instance určeného v kroku výše.
    • V opačném případě je object_creation_expression neplatný a dojde k chybě při vazbě.

I když je object_creation_expression dynamicky vázán, typ kompilace je stále T.

Zpracování za běhu object_creation_expression tvaru new T(A), kde T je class_type nebo struct_type a A je volitelný argument_list, se skládá z následujících kroků:

  • Pokud je třída typu:
    • Je přidělena nová instance třídy T. Pokud není k dispozici dostatek paměti pro přidělení nové instance, vyvolá se System.OutOfMemoryException a neprojdou žádné další kroky.
    • Všechna pole nové instance jsou inicializována na výchozí hodnoty (§9.3).
    • Konstruktor instance je vyvolán podle pravidel vyvolání člena funkce (§12.6.6). Odkaz na nově přidělenou instanci se automaticky předá konstruktoru instance a instance může být přístupná z tohoto konstruktoru.
  • Pokud je Tstruktura typu:
    • Instance typu T je vytvořena přidělením dočasné místní proměnné. Protože instance konstruktoru typu struktury musí jednoznačně přiřadit hodnotu každému poli vytvářené instance, není nutné žádné inicializování pomocné proměnné.
    • Konstruktor instance je vyvolán podle pravidel vyvolání člena funkce (§12.6.6). Odkaz na nově přidělenou instanci se automaticky předá konstruktoru instance a instance může být přístupná z tohoto konstruktoru.

12.8.17.3 Inicializátory objektů

Inicializátor objektů určuje hodnoty pro nula nebo více polí, vlastností nebo indexovaných prvků objektu.

object_initializer
    : '{' member_initializer_list? '}'
    | '{' member_initializer_list ',' '}'
    ;

member_initializer_list
    : member_initializer (',' member_initializer)*
    ;

member_initializer
    : initializer_target '=' initializer_value
    ;

initializer_target
    : identifier
    | '[' argument_list ']'
    ;

initializer_value
    : expression
    | object_or_collection_initializer
    ;

Inicializátor objektu se skládá z posloupnosti inicializátorů členů, které jsou uzavřeny do { a } tokenů a odděleny čárkami. Každý member_initializer určí cíl inicializace. Identifikátor pojmenuje přístupné pole nebo vlastnost inicializovaného objektu, zatímco argument_list uzavřená v hranatých závorkách určuje argumenty pro přístupný indexer inicializovaného objektu. Jedná se o chybu, kdy inicializátor objektů obsahuje více než jeden inicializátor členu pro stejné pole nebo vlastnost.

Poznámka: I když inicializátor objektů není povoleno nastavit stejné pole nebo vlastnost více než jednou, neexistují žádná taková omezení pro indexery. Inicializátor objektů může obsahovat více cílů inicializátoru odkazujících na indexery a může dokonce používat stejné argumenty indexeru vícekrát. koncová poznámka

Za každým initializer_target následuje znaménko rovná se a buď výraz, inicializátor objektu, nebo inicializátor kolekce. Není možné, aby výrazy v inicializátoru objektu odkazovaly na nově vytvořený objekt, který inicializují.

Inicializátor členu určuje výraz, který následuje po rovnítku, a je zpracován stejným způsobem, jako by se jednalo o přiřazení (§12.21.2) na cíl.

Inicializátor členu, který určuje inicializátor objektu nacházející se za znaménkem rovnosti, je vnořený inicializátor objektu, tj. inicializace vloženého objektu. Namísto přiřazení nové hodnoty k poli nebo vlastnosti se přiřazení v inicializátoru vnořeného objektu považují za přiřazení členům pole nebo vlastnosti. Inicializátory vnořených objektů nelze použít u vlastností s typem hodnoty ani u polí jen pro čtení s typem hodnoty.

Inicializátor člena, který určuje inicializátor kolekce za znaménkem rovná se je inicializace vložené kolekce. Místo přiřazení nové kolekce k cílovému poli, vlastnosti nebo indexeru se prvky uvedené v inicializátoru přidají do kolekce odkazované cílem. Cílem je typ sběru, který splňuje požadavky stanovené v §12.8.17.4.

Pokud cíl inicializátoru odkazuje na indexer, musí být argumenty indexeru vždy vyhodnoceny přesně jednou. Proto, i když se argumenty nikdy nepoužijí (např. kvůli prázdnému vnořenému inicializátoru), zvažovány jsou kvůli svým vedlejším efektům.

Příklad: Následující třída představuje bod se dvěma souřadnicemi:

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

Instanci Point lze vytvořit a inicializovat následujícím způsobem:

Point a = new Point { X = 0, Y = 1 };

To má stejný účinek jako

Point __a = new Point();
__a.X = 0;
__a.Y = 1;
Point a = __a;

kde __a je jinak neviditelná a nepřístupná dočasná proměnná.

Následující třída ukazuje obdélník vytvořený ze dvou bodů a vytvoření a inicializaci instance Rectangle:

public class Rectangle
{
    public Point P1 { get; set; }
    public Point P2 { get; set; }
}

Instanci Rectangle lze vytvořit a inicializovat následujícím způsobem:

Rectangle r = new Rectangle
{
    P1 = new Point { X = 0, Y = 1 },
    P2 = new Point { X = 2, Y = 3 }
};

To má stejný účinek jako

Rectangle __r = new Rectangle();
Point __p1 = new Point();
__p1.X = 0;
__p1.Y = 1;
__r.P1 = __p1;
Point __p2 = new Point();
__p2.X = 2;
__p2.Y = 3;
__r.P2 = __p2;
Rectangle r = __r;

kde __r, __p1 a __p2 jsou dočasné proměnné, které jsou jinak neviditelné a nepřístupné.

Pokud konstruktor Rectanglepřiděluje dvě vložené Point instance, je možné je použít k inicializaci vložených Point instancí místo přiřazování nových instancí:

public class Rectangle
{
    public Point P1 { get; } = new Point();
    public Point P2 { get; } = new Point();
}

Pomocí následujícího konstruktoru lze inicializovat vložené Point instance místo přiřazování nových instancí:

Rectangle r = new Rectangle
{
    P1 = { X = 0, Y = 1 },
    P2 = { X = 2, Y = 3 }
};

To má stejný účinek jako

Rectangle __r = new Rectangle();
__r.P1.X = 0;
__r.P1.Y = 1;
__r.P2.X = 2;
__r.P2.Y = 3;
Rectangle r = __r;

koncového příkladu

12.8.17.4 Inicializátory kolekcí

Inicializátor kolekce určuje prvky kolekce.

collection_initializer
    : '{' element_initializer_list '}'
    | '{' element_initializer_list ',' '}'
    ;

element_initializer_list
    : element_initializer (',' element_initializer)*
    ;

element_initializer
    : non_assignment_expression
    | '{' expression_list '}'
    ;

expression_list
    : expression
    | expression_list ',' expression
    ;

Inicializátor kolekce se skládá z posloupnosti inicializátorů prvků ohraničených tokeny { a } a oddělených čárkami. Každý inicializátor elementu určuje prvek, který se má přidat do inicializovaného objektu kolekce, a skládá se ze seznamu výrazů uzavřených { a } tokeny a oddělené čárkami. Inicializátor elementu s jedním výrazem lze zapsat bez závorek, ale nesmí být přiřazovacím výrazem, aby nedocházelo k nejednoznačnosti s inicializátory členů. Produkce non_assignment_expression je definována v §12.22.

Příklad: Následuje příklad výrazu pro vytvoření objektu, který obsahuje inicializátor kolekce:

List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

konec příkladu

Objekt kolekce, na který se použije inicializátor kolekce, musí být typu, který implementuje System.Collections.IEnumerable nebo dojde k chybě v době kompilace. Pro každý zadaný prvek v pořadí odleva doprava se normální vyhledávání členů použije k vyhledání členu s názvem Add. Pokud výsledek vyhledávání člena není skupina metod, dojde k chybě v době kompilace. V opačném případě je vyhodnocení přetížení aplikováno s výrazovým seznamem inicializátoru prvku jako seznamem argumentů a inicializátor kolekce vyvolá výslednou metodu. Objekt kolekce tedy musí obsahovat platnou instanci nebo rozšiřující metodu s názvem Add pro každý inicializátor prvku.

Příklad: Následující příklad ukazuje třídu, která představuje kontakt se jménem a seznamem telefonních čísel, a jak vytvořit a inicializovat objekt List<Contact>:

public class Contact
{
    public string Name { get; set; }
    public List<string> PhoneNumbers { get; } = new List<string>();
}

class A
{
    static void M()
    {
        var contacts = new List<Contact>
        {
            new Contact
            {
                Name = "Chris Smith",
                PhoneNumbers = { "206-555-0101", "425-882-8080" }
            },
            new Contact
            {
                Name = "Bob Harris",
                PhoneNumbers = { "650-555-0199" }
            }
        };
    }
}

který má stejný účinek jako

var __clist = new List<Contact>();
Contact __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
__clist.Add(__c1);
Contact __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
__clist.Add(__c2);
var contacts = __clist;

kde __clist, __c1 a __c2 jsou dočasné proměnné, které jsou jinak neviditelné a nepřístupné.

konec příkladu

12.8.17.5 Výrazy pro vytváření polí

array_creation_expression se používá k vytvoření nové instance array_type.

array_creation_expression
    : 'new' non_array_type '[' expression_list ']' rank_specifier*
      array_initializer?
    | 'new' array_type array_initializer
    | 'new' rank_specifier array_initializer
    ;

Výraz vytvoření pole prvního tvaru přidělí instanci pole toho typu, který vznikne odstraněním každého z jednotlivých výrazů ze seznamu výrazů.

Příklad: Výraz pro vytvoření pole new int[10,20] vytvoří instanci pole typu int[,]a nový výraz pro vytvoření pole int[10][,] vytvoří instanci pole typu int[][,]. konec příkladu

Každý výraz v seznamu výrazů musí být typu int, uint, longnebo ulongnebo implicitně konvertibilní na jeden nebo více těchto typů. Hodnota každého výrazu určuje délku odpovídající dimenze v nově přidělené instanci pole. Vzhledem k tomu, že délka rozměru pole musí být nezáporná, je v době kompilace chybou mít v seznamu výrazů konstantní výraz se zápornou hodnotou.

S výjimkou nebezpečného kontextu (§ 23.2), rozložení polí není určeno.

Pokud výraz vytvoření pole prvního formuláře obsahuje inicializátor pole, musí být každý výraz v seznamu výrazů konstantou a délkami pořadí a dimenzí určenými seznamem výrazů musí odpovídat hodnotám inicializátoru pole.

Ve výrazu vytvoření pole druhé nebo třetí formy se pořadí zadaného typu pole nebo specifikátoru pořadí musí shodovat s inicializátorem pole. Jednotlivé délky dimenzí jsou odvozeny z počtu prvků v každé z odpovídajících úrovní vnoření inicializátoru pole. Výraz inicializátoru tedy v následující deklaraci

var a = new int[,] {{0, 1}, {2, 3}, {4, 5}};

přesně odpovídá

var a = new int[3, 2] {{0, 1}, {2, 3}, {4, 5}};

Výraz vytvoření pole třetího typu se označuje jako implicitně zadaný výraz pro vytvoření pole. Podobá se druhé formě s tím rozdílem, že typ prvku pole není explicitně uveden, ale určen jako nejlepší společný typ (§12.6.3.15) sady výrazů v inicializátoru pole. Pro multidimenzionální pole, tj. jedno, kde rank_specifier obsahuje aspoň jednu čárku, se tato sada skládá ze všech výrazů nalezených ve vnořených array_initializers.

Inicializátory pole jsou popsány dále v §17.7.

Výsledek vyhodnocení výrazu vytvoření pole je klasifikován jako hodnota, konkrétně odkaz na nově přidělenou instanci pole. Zpracování za běhu výrazu pro vytvoření pole se skládá z následujících kroků:

  • Výrazy délky dimenzí expression_list se vyhodnocují v pořadí zleva doprava. Po vyhodnocení každého výrazu se provádí implicitní převod (§10.2) na jeden z následujících typů: int, uint, long, ulong. První typ v tomto seznamu, pro který existuje implicitní převod, je vybrán. Pokud vyhodnocení výrazu nebo následné implicitní převody způsobí výjimku, nevyhodnotí se žádné další výrazy a nebudou provedeny žádné další kroky.
  • Vypočítané hodnoty délky dimenzí se ověřují následujícím způsobem: Pokud je jedna nebo více hodnot menší než nula, vyvolá se System.OverflowException a neprojdou žádné další kroky.
  • Je přidělena instance pole s danou délkou dimenze. Pokud není k dispozici dostatek paměti pro přidělení nové instance, vyvolá se System.OutOfMemoryException a neprojdou žádné další kroky.
  • Všechny prvky nové instance pole jsou inicializovány na výchozí hodnoty (§9.3).
  • Pokud výraz pro vytvoření pole obsahuje inicializátor pole, je každý výraz v inicializátoru pole vyhodnocen a přiřazen k příslušnému prvku pole. Vyhodnocení a přiřazení se provádějí v pořadí, v jakém se výrazy zapisují v inicializátoru pole – jinými slovy, prvky se inicializují v rostoucím pořadí indexu, přičemž první dimenze úplně vpravo se zvyšuje. Pokud vyhodnocení daného výrazu nebo následného přiřazení k odpovídajícímu prvku pole způsobí výjimku, nebudou inicializovány žádné další prvky (a zbývající prvky tak budou mít výchozí hodnoty).

Výraz vytvoření pole umožňuje vytvořit instanci pole s prvky typu pole, ale prvky takového pole musí být inicializovány ručně.

Příklad: Příkaz

int[][] a = new int[100][];

vytvoří jednorozměrné pole s 100 prvky typu int[]. Počáteční hodnota každého prvku je null. Není možné, aby stejný výraz pro vytvoření pole také instancoval dílčí pole.

int[][] a = new int[100][5]; // Error

výsledkem je chyba v době kompilace. Vytvoření instance dílčích polí je možné místo toho provést ručně, například v

int[][] a = new int[100][];
for (int i = 0; i < 100; i++)
{
    a[i] = new int[5];
}

konec příkladu

Poznámka: Pokud má matice polí obdélníkový tvar, je to v případě, že jsou podmatice všechny stejné délky, je efektivnější použít vícerozměrné pole. V předchozím příkladu instanciace pole polí vytvoří 101 objektů – jedno vnější pole a 100 podpolí. Na rozdíl

int[,] a = new int[100, 5];

vytvoří pouze jeden objekt, dvojrozměrné pole a provede přidělení v jednom příkazu.

koncová poznámka

Příklad: Následující příklady implicitně zadaných výrazů vytváření pole:

var a = new[] { 1, 10, 100, 1000 };                     // int[]
var b = new[] { 1, 1.5, 2, 2.5 };                       // double[]
var c = new[,] { { "hello", null }, { "world", "!" } }; // string[,]
var d = new[] { 1, "one", 2, "two" };                   // Error

Poslední výraz způsobí chybu v době kompilace, protože int ani string nejsou implicitně konvertibilní na druhý, a proto neexistuje nejlepší společný typ. V tomto případě musí být použit explicitně zadaný výraz pro vytvoření pole, například určení typu, který má být object[]. Alternativně lze jeden z prvků přetypovat na běžný základní typ, který by se pak stal odvozeným typem elementu.

konec příkladu

Implicitně typované výrazy vytváření pole lze kombinovat s anonymními inicializátory objektů (§12.8.17.7) k vytvoření anonymně typovaných datových struktur.

Příklad :

var contacts = new[]
{
    new
    {
        Name = "Chris Smith",
        PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
    },
    new 
    {
        Name = "Bob Harris",
       PhoneNumbers = new[] { "650-555-0199" }
    }
};

konec příkladu

12.8.17.6 Výrazy vytváření delegátů

K získání instance delegate_typese používá delegate_creation_expression .

delegate_creation_expression
    : 'new' delegate_type '(' expression ')'
    ;

Argumentem výrazu vytvoření delegáta musí být skupina metod, anonymní funkce, nebo hodnota buď typu stanoveného v době kompilace dynamic, nebo typ delegáta . Pokud je argumentem skupina metod, identifikuje metodu a v případě metody instance objekt, pro který se má vytvořit delegát. Pokud je argument anonymní funkcí, přímo definuje parametry a tělo metody cíle delegáta. Pokud je argument hodnotou, identifikuje instanci delegáta, ze které se má vytvořit kopie.

Pokud výraz má typ kompilace dynamic, je výraz delegate_creation dynamicky vázán (§12.8.17.6), a následující pravidla se použijí za běhu s použitím běhového typu výrazu . V opačném případě se pravidla použijí v době kompilace.

Zpracování vazby delegate_creation_expression ve tvaru new D(E), kde D je delegate_type a E je výraz, se skládá z následujících kroků:

  • Pokud E je skupina metod, výraz vytvoření delegáta se zpracuje stejným způsobem jako převod skupiny metod (§10,8) z E na D.

  • Je-li E anonymní funkcí, výraz vytvoření delegáta se zpracuje stejným způsobem jako anonymní převod funkce (§10,7) z E na D.

  • Je-li E hodnotou, E musí být kompatibilní (§20.2) s Da výsledkem je odkaz na nově vytvořený delegát s jedním vstupním seznamem vyvolání, který vyvolá E.

Zpracování běhu delegate_creation_expression ve tvaru new D(E), kde D je delegate_type a E je výraz, se skládá z následujících kroků:

  • Pokud E je skupina metod, výraz vytvoření delegáta se vyhodnotí jako převod skupiny metod (§10.8) z E na D.
  • Pokud E je anonymní funkce, je vytvoření delegáta vyhodnoceno jako anonymní převod funkce z E na D (§10,7).
  • Pokud je E hodnotou typu delegáta :
    • E se vyhodnotí. Pokud toto vyhodnocení způsobí výjimku, neprojdou žádné další kroky.
    • Pokud je hodnota Enull, vyvolá se System.NullReferenceException a neprovedou se žádné další kroky.
    • Je přidělena nová instance typu delegáta D. Pokud není k dispozici dostatek paměti pro přidělení nové instance, vyvolá se System.OutOfMemoryException a neprojdou žádné další kroky.
    • Nová instance delegáta se inicializuje s jednopoložkovým seznamem volání, který vyvolá E.

Seznam vyvolání delegáta se určí, když se delegát instancuje, a zůstane konstantní po celou dobu existence delegáta. Jinými slovy, po vytvoření delegáta není možné změnit cílové volatelné entity delegáta.

Poznámka: Nezapomeňte, že pokud jsou dva delegáti zkombinováni nebo jeden z nich je odebrán z jiného, vznikne nový delegát; žádný existující delegát nemá změněný obsah. koncová poznámka

Není možné vytvořit delegát, který odkazuje na vlastnost, indexer, uživatelem definovaný operátor, konstruktor instance, finalizátor nebo statický konstruktor.

Příklad: Jak je popsáno výše, když je delegát vytvořen ze skupiny metod, seznam parametrů a návratový typ delegáta určují, které z přetížených metod se mají vybrat. V příkladu

delegate double DoubleFunc(double x);

class A
{
    DoubleFunc f = new DoubleFunc(Square);

    static float Square(float x) => x * x;
    static double Square(double x) => x * x;
}

pole A.f je inicializováno delegátem, který odkazuje na druhou metodu Square, protože tato metoda přesně odpovídá seznamu parametrů a návratovému typu DoubleFunc. Pokud druhá Square metoda nebyla přítomna, došlo k chybě v době kompilace.

konec příkladu

12.8.17.7 Výrazy pro vytváření anonymních objektů

anonymous_object_creation_expression se používá k vytvoření objektu anonymního typu.

anonymous_object_creation_expression
    : 'new' anonymous_object_initializer
    ;

anonymous_object_initializer
    : '{' member_declarator_list? '}'
    | '{' member_declarator_list ',' '}'
    ;

member_declarator_list
    : member_declarator (',' member_declarator)*
    ;

member_declarator
    : simple_name
    | member_access
    | null_conditional_projection_initializer
    | base_access
    | identifier '=' expression
    ;

Inicializátor anonymního objektu deklaruje anonymní typ a vrátí instanci tohoto typu. Anonymní typ je beznázvový typ třídy, který dědí přímo z object. Členy anonymního typu jsou posloupnost vlastností jen pro čtení odvozených z inicializátoru anonymního objektu použitého k vytvoření instance typu. Konkrétně anonymní inicializátor objektu formuláře

new { p₁=e₁,p₂=e₂, ... pv=ev}

deklaruje anonymní typ formuláře.

class __Anonymous1
{
    private readonly «T1» «f1»;
    private readonly «T2» «f2»;
    ...
    private readonly «Tn» «fn»;

    public __Anonymous1(«T1» «a1», «T2» «a2»,..., «Tn» «an»)
    {
        «f1» = «a1»;
        «f2» = «a2»;
        ...
        «fn» = «an»;
    }

    public «T1» «p1» { get { return «f1»; } }
    public «T2» «p2» { get { return «f2»; } }
    ...
    public «Tn» «pn» { get { return «fn»; } }
    public override bool Equals(object __o) { ... }
    public override int GetHashCode() { ... }
}

kde každý «Tx» je typ odpovídajícího výrazu «ex». Výraz použitý v member_declarator musí mít typ. Jedná se tedy o chybu při kompilaci, pokud je výraz v member_declaratornull nebo anonymní funkcí.

Názvy anonymního typu a parametru pro jeho Equals metodu jsou automaticky generovány kompilátorem a nelze na to odkazovat v textu programu.

Ve stejném programu dva inicializátory anonymních objektů, které určují posloupnost vlastností stejných názvů a typů kompilátoru ve stejném pořadí, vytvoří instance stejného anonymního typu.

Příklad: V příkladu

var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;

přiřazení na posledním řádku je povoleno, protože p1 a p2 jsou stejného anonymního typu.

konec příkladu

Metody Equals a GetHashcode u anonymních typů přepíší metody zděděné z objecta jsou definovány z hlediska Equals a GetHashcode vlastností, aby byly dvě instance stejného anonymního typu stejné, a to pouze v případě, že jsou všechny jejich vlastnosti stejné.

Deklarátor člena lze zkrátit na jednoduchý název (§12.8.4), přístup člena (§12.8.7), inicializátor podmíněné projekce null §12.8.8 nebo základní přístup (§12.8.15). To se nazývá inicializátor projekce a je zkratka pro deklaraci a přiřazení vlastnosti se stejným názvem. Konkrétně členské deklarátory formulářů

«identifier», «expr» . «identifier» a «expr» ? . «identifier»

jsou přesně ekvivalentní následujícímu:

«identifer» = «identifier», «identifier» = «expr» . «identifier» a «identifier» = «expr» ? . «identifier»

Proto v inicializátoru projekce identifikátor vybere hodnotu i pole nebo vlastnost, ke které je hodnota přiřazena. Intuitivně inicializátor projekce projektuje nejen hodnotu, ale také název hodnoty.

12.8.18 Operátor typeof

Operátor typeof slouží k získání System.Type objektu pro typ.

typeof_expression
    : 'typeof' '(' type ')'
    | 'typeof' '(' unbound_type_name ')'
    | 'typeof' '(' 'void' ')'
    ;

unbound_type_name
    : identifier generic_dimension_specifier?
    | identifier '::' identifier generic_dimension_specifier?
    | unbound_type_name '.' identifier generic_dimension_specifier?
    ;

generic_dimension_specifier
    : '<' comma* '>'
    ;

comma
    : ','
    ;

První forma typeof_expression se skládá z typeof klíčového slova následovaného typem uzavřeným v závorkách. Výsledkem výrazu tohoto formuláře je System.Type objekt pro zadaný typ. Pro každý daný typ existuje pouze jeden objekt System.Type. To znamená, že pro typ Tje typeof(T) == typeof(T) vždy pravdivý. Typ nemůže být dynamic.

Druhá forma typeof_expression se skládá z typeof klíčového slova následovaného závorkou unbound_type_name.

Poznámka: unbound_type_name se velmi podobá type_name (§7.8) s tím rozdílem, že unbound_type_name obsahuje generic_dimension_specifiers místem, kde type_name obsahuje type_argument_lists. koncová poznámka

Pokud je operand typeof_expression posloupnost tokenů, která splňuje gramatiky unbound_type_name i type_name, a to v případě, že neobsahuje generic_dimension_specifier ani type_argument_list, je posloupnost tokenů považována za type_name. Význam unbound_type_name se určuje takto:

  • Převeďte posloupnost tokenů na type_name tím, že nahradíte každý generic_dimension_specifier s type_argument_list, který má stejný počet čárek a klíčové slovo object jako každý type_argument.
  • Vyhodnoťte výslednou type_namea současně ignorujte všechna omezení parametrů typu.
  • unbound_type_name se odkazuje na nevázaný obecný typ přidružený k výslednému konstruovanému typu (§8.4).

Je chybou, aby název typu byl nulovatelným referenčním typem.

Výsledkem typeof_expression je objekt System.Type pro výsledný nevázaný obecný typ.

Třetí forma typeof_expression se skládá z typeof klíčového slova následovaného vloženým do závorek void klíčovým slovem. Výsledkem výrazu této formy je objekt System.Type, který představuje nepřítomnost typu. Objekt typu vrácený typeof(void) se liší od objektu typu vráceného pro libovolný typ.

Poznámka: Tento speciální objekt System.Type je užitečný v knihovnách tříd, které umožňují reflexi na metody v daném jazyce. Tyto metody chtějí mít způsob, jak reprezentovat návratový typ jakékoli metody, včetně metod void, s instancí System.Type. koncová poznámka

Operátor typeof lze použít u parametru typu. Jedná se o chybu v době kompilace, pokud je název typu známý jako typ odkazu s možnou hodnotou null. Výsledkem je System.Type objekt pro typ běhu, který byl vázán na parametr typu. Pokud je běhový typ typu odkaz s možnou hodnotou null, je výsledkem odpovídající typ odkazu, který není nulovatelný. Operátor typeof lze použít také u typu konstrukce nebo nevázaného obecného typu (§8.4.4). Objekt System.Type pro nevázaný obecný typ není stejný jako objekt System.Type typu instance (§15.3.2). Typ instance je vždy uzavřený typ za běhu, takže jeho System.Type objekt závisí na argumentech typu za běhu. Nevázaný obecný typ na druhé straně nemá žádné argumenty typu a poskytuje stejný System.Type objekt bez ohledu na argumenty typu modulu runtime.

Příklad: Příklad

class X<T>
{
    public static void PrintTypes()
    {
        Type[] t =
        {
            typeof(int),
            typeof(System.Int32),
            typeof(string),
            typeof(double[]),
            typeof(void),
            typeof(T),
            typeof(X<T>),
            typeof(X<X<T>>),
            typeof(X<>)
        };
        for (int i = 0; i < t.Length; i++)
        {
            Console.WriteLine(t[i]);
        }
    }
}

class Test
{
    static void Main()
    {
        X<int>.PrintTypes();
    }
}

vytvoří následující výstup:

System.Int32
System.Int32
System.String
System.Double[]
System.Void
System.Int32
X`1[System.Int32]
X`1[X`1[System.Int32]]
X`1[T]

Všimněte si, že int a System.Int32 jsou stejného typu. Výsledek typeof(X<>) nezávisí na argumentu typu, ale výsledek typeof(X<T>) ano.

koncového příkladu

12.8.19 Operátor sizeof

Operátor sizeof vrátí počet 8bitových bajtů obsazených proměnnou daného typu. Typ určený jako operand pro velikost musí být unmanaged_type (§8.8).

sizeof_expression
    : 'sizeof' '(' unmanaged_type ')'
    ;

U některých předdefinovaných typů operátor sizeof poskytuje konstantní int hodnotu, jak je znázorněno v následující tabulce:

výraz Výsledek
sizeof(sbyte) 1
sizeof(byte) 1
sizeof(short) 2
sizeof(ushort) 2
sizeof(int) 4
sizeof(uint) 4
sizeof(long) 8
sizeof(ulong) 8
sizeof(char) 2
sizeof(float) 4
sizeof(double) 8
sizeof(bool) 1
sizeof(decimal) 16

Pro typ výčtu Tje výsledek výrazu sizeof(T) konstantní hodnota, která se rovná velikosti jeho základního typu, jak je uvedeno výše. Pro všechny ostatní typy operandů je operátor sizeof uveden v §23.6.9.

12.8.20 Zaškrtnuté a nezaškrtnuté operátory

Operátory checked a unchecked slouží k řízení kontextu kontroly přetečení pro aritmetické operace a převody celočíselného typu.

checked_expression
    : 'checked' '(' expression ')'
    ;

unchecked_expression
    : 'unchecked' '(' expression ')'
    ;

Operátor checked vyhodnotí obsažený výraz v kontrolovaném kontextu a operátor unchecked vyhodnotí obsažený výraz v nezaškrtnutém kontextu. checked_expression nebo unchecked_expression se přesně shoduje s parenthesized_expression (§12.8.5), s tím rozdílem, že obsažený výraz je hodnocen v daném kontextu kontroly přetečení.

Kontext kontroly přetečení lze také řídit prohlášeními checked a unchecked (§13.12).

Následující operace jsou ovlivněny kontextem kontroly přetečení vytvořeným kontrolovanými a nekontrolovanými operátory a příkazy:

  • Předdefinované operátory ++ a -- (§12.8.16 a §12.9.6), pokud je operand celočíselného typu nebo výčtu.
  • Předdefinovaný - unární operátor (§12.9.3), pokud je operand celočíselného typu.
  • Předdefinované +, -, *a / binární operátory (§12.10), pokud jsou oba operandy celočíselné nebo výčtové typy.
  • Explicitní číselné převody (§10.3.2) z jednoho integrálního nebo výčtového typu na jiný integrální nebo výčtový typ, nebo z float nebo double na celočíselný nebo výčtový typ.

Pokud jedna z výše uvedených operací vytvoří výsledek, který je příliš velký, aby byl reprezentován v cílovém typu, kontext, ve kterém je operace prováděna, řídí výsledného chování:

  • V kontextu checked, pokud je operace konstantním výrazem (§12.23), nastane chyba při kompilaci. Jinak se při provedení operace za běhu vyvolá System.OverflowException.
  • V kontextu unchecked se výsledek zkrátí tak, že zahodí všechny bity s vysokým pořadím, které se nevejdou do cílového typu.

Pro ne constantní výrazy (§12.23) (výrazy vyhodnocené za běhu), které nejsou uzavřeny žádnými checked nebo unchecked operátory nebo příkazy, není výchozí kontext kontroly přetečení nezaškrtnut, pokud nejsou zaškrtnuté externí faktory (například přepínače kompilátoru a konfigurace prostředí spouštění) pro kontrolované vyhodnocení.

U konstantních výrazů (§12.23) (výrazy, které lze plně vyhodnotit v době kompilace), je vždy kontrolován výchozí kontext kontroly přetečení. Pokud není konstantní výraz explicitně umístěn v kontextu unchecked, přetečení, ke kterým dochází při vyhodnocování doby kompilace výrazu, vždy způsobí chyby kompilace.

Tělo anonymní funkce není ovlivněno checked nebo unchecked kontexty, ve kterých se anonymní funkce vyskytuje.

příklad: V následujícím kódu

class Test
{
    static readonly int x = 1000000;
    static readonly int y = 1000000;

    static int F() => checked(x * y);    // Throws OverflowException
    static int G() => unchecked(x * y);  // Returns -727379968
    static int H() => x * y;             // Depends on default
}

Nejsou hlášeny žádné chyby v době kompilace, protože ani jeden z výrazů nelze vyhodnotit v době kompilace. Při běhu programu vyvolá metoda FSystem.OverflowException, a metoda G vrátí –727379968 (32 nejnižších bitů výsledku mimo rozsah). Chování metody H závisí na výchozím kontextu kontroly přetečení pro kompilaci, ale je to buď stejné jako F, nebo stejné jako G.

konec příkladu

příklad: V následujícím kódu

class Test
{
    const int x = 1000000;
    const int y = 1000000;

    static int F() => checked(x * y);    // Compile-time error, overflow
    static int G() => unchecked(x * y);  // Returns -727379968
    static int H() => x * y;             // Compile-time error, overflow
}

přetečení, která nastávají při hodnocení konstantních výrazů v F a H, způsobují nahlášení chyb v době kompilace, protože výrazy se hodnotí v kontextu checked. K přetečení dochází také při vyhodnocování konstantního výrazu v G, ale protože vyhodnocení probíhá v kontextu unchecked, přetečení není hlášeno.

konec příkladu

Operátory checked a unchecked mají vliv pouze na kontext kontroly přetečení pro operace, které jsou textově obsažené v tokenech(a). Operátory nemají žádný vliv na členy funkce, které jsou vyvolány v důsledku vyhodnocení obsaženého výrazu.

příklad: V následujícím kódu

class Test
{
    static int Multiply(int x, int y) => x * y;

    static int F() => checked(Multiply(1000000, 1000000));
}

použití checked v F nemá vliv na vyhodnocení x * y v Multiply, takže x * y je vyhodnocen ve výchozím kontextu kontroly přetečení.

konec příkladu

Operátor unchecked je vhodný při psaní konstant celočíselných typů se znaménkovým zápisem.

příklad :

class Test
{
    public const int AllBits = unchecked((int)0xFFFFFFFF);
    public const int HighBit = unchecked((int)0x80000000);
}

Oba šestnáctkové konstanty výše jsou typu uint. Vzhledem k tomu, že konstanty jsou mimo rozsah int, bez operátoru unchecked by přetypování na int vytvářelo chyby v době kompilace.

konec příkladu

Poznámka: Operátory a příkazy checked a unchecked umožňují programátorům řídit určité aspekty některých číselných výpočtů. Chování některých číselných operátorů ale závisí na datových typech operandů. Například vynásobením dvou desetinných čísel vždy dojde k výjimce při přetečení, i v rámci explicitně neověřené konstrukce. Podobně při vynásobení dvou plovoucích desetinných čísel nedochází k výjimce při přetečení, a to ani v rámci explicitně kontrolované konstrukce. Kromě toho nejsou ostatní operátory nikdy ovlivněny kontrolou, ať už výchozí nebo explicitní. koncová poznámka

12.8.21 Výrazy výchozí hodnoty

Výchozí výraz hodnoty slouží k získání výchozí hodnoty (§9.3) typu.

default_value_expression
    : explictly_typed_default
    | default_literal
    ;

explictly_typed_default
    : 'default' '(' type ')'
    ;

default_literal
    : 'default'
    ;

default_literal představuje výchozí hodnotu (§9,3). Nemá typ, ale lze jej převést na jakýkoli typ pomocí výchozího literálového převodu (§10.2.16).

Výsledkem default_value_expression je výchozí hodnota (§9.3) explicitního typu v explicitně_typovaném_výchozímnebo cílový typ převodu pro default_value_expression.

default_value_expression je konstantní výraz (§12,23) pokud je typ jedním z:

  • referenční typ
  • parametr typu, který je znám jako referenční typ (§8.2);
  • jeden z následujících typů hodnot: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool,; nebo
  • libovolný typ výčtu.

12.8.22 Alokace zásobníku

Výraz přidělení zásobníku přiděluje blok paměti ze zásobníku spouštění. Zásobník spouštění je oblast paměti, ve které jsou uloženy místní proměnné. Vykonávací zásobník není součástí spravované haldy. Paměť používaná pro místní úložiště proměnných se automaticky obnoví při vrácení aktuální funkce.

Pravidla bezpečného kontextu výrazu pro alokaci zásobníku jsou popsána v §16.4.12.7.

stackalloc_expression
    : 'stackalloc' unmanaged_type '[' expression ']'
    | 'stackalloc' unmanaged_type? '[' constant_expression? ']' stackalloc_initializer
    ;

stackalloc_initializer
     : '{' stackalloc_initializer_element_list '}'
     ;

stackalloc_initializer_element_list
     : stackalloc_element_initializer (',' stackalloc_element_initializer)* ','?
     ;
    
stackalloc_element_initializer
    : expression
    ;

unmanaged_type (§8.8) označuje typ položek, které budou uloženy v nově přiděleném umístění, a výraz označuje počet těchto položek. Společně určují požadovanou velikost přidělení. Typ výrazu musí být implicitně konvertibilní na typ int.

Vzhledem k tomu, že velikost přidělení zásobníku nemůže být záporná, jedná se o chybu v době kompilace, která určuje počet položek jako constant_expression, která se vyhodnotí jako záporná hodnota.

Za běhu, pokud počet položek, které se mají přidělit, je záporná hodnota, pak chování není definováno. Pokud je nula, pak se neprovedou žádné přidělení a vrácená hodnota je definována implementací. Pokud není k dispozici dostatek paměti pro přidělení položek, vyvolá se System.StackOverflowException.

Když je k dispozici stackalloc_initializer:

  • Pokud unmanaged_type vynecháte, je odvozeno podle pravidel pro nejlepší společný typ (§12.6.3.15) pro sadu stackalloc_element_initializers.
  • Pokud constant_expression vynecháte, je odvozeno, že se jedná o počet stackalloc_element_initializers.
  • Je-li constant_expression přítomna, rovná se počtu stackalloc_element_initializers.

Každý stackalloc_element_initializer má implicitní převod na unmanaged_type (§10.2). stackalloc_element_initializerinicializuje prvky v přidělené paměti v rostoucím pořadí, počínaje prvkem na indexu nula. V nepřítomnosti stackalloc_initializernení definován obsah nově přidělené paměti.

Pokud stackalloc_expression se vyskytne přímo jako inicializační výraz local_variable_declaration (§13.6.2), kde local_variable_type je buď ukazatel typu (§23.3) nebo odvozený (var), výsledkem výrazu stackalloc_expression je ukazatel typu T* (§23.9). V takovém případě se stackalloc_expression musí objevit v nebezpečném kódu. V opačném případě je výsledkem stackalloc_expression instance typu Span<T>, kde T je unmanaged_type:

  • Span<T> (§C.3) je typ ref struktury (§16.2.3), který reprezentuje blok paměti, zde blok přidělený stackalloc_expression, jako indexovatelnou kolekci položek (T).
  • Vlastnost Length výsledku vrátí počet přidělených položek.
  • Indexer výsledku (§15,9) vrátí variable_reference (§9,5) k položce přiděleného bloku a je kontrolován rozsah.

Inicializátory přidělování zásobníku nejsou povoleny v blocích catch nebo finally (§13.11).

Poznámka: Neexistuje způsob, jak explicitně uvolnit paměť přidělenou pomocí stackalloc. koncová poznámka

Všechny bloky paměti přidělené zásobníkem vytvořené během provádění člena funkce se automaticky zahodí, když tento člen funkce vrátí.

Kromě operátoru stackalloc jazyk C# neposkytuje žádné předdefinované konstrukce pro správu paměti, která není spravována docházkovým sběračem. Tyto služby jsou obvykle poskytovány podporou knihoven tříd nebo importovány přímo ze základního operačního systému.

Příklad :

// Memory uninitialized
Span<int> span1 = stackalloc int[3];
// Memory initialized
Span<int> span2 = stackalloc int[3] { -10, -15, -30 };
// Type int is inferred
Span<int> span3 = stackalloc[] { 11, 12, 13 };
// Error; result is int*, not allowed in a safe context
var span4 = stackalloc[] { 11, 12, 13 };
// Error; no conversion from Span<int> to Span<long>
Span<long> span5 = stackalloc[] { 11, 12, 13 };
// Converts 11 and 13, and returns Span<long> 
Span<long> span6 = stackalloc[] { 11, 12L, 13 };
// Converts all and returns Span<long>
Span<long> span7 = stackalloc long[] { 11, 12, 13 };
// Implicit conversion of Span<T>
ReadOnlySpan<int> span8 = stackalloc int[] { 10, 22, 30 };
// Implicit conversion of Span<T>
Widget<double> span9 = stackalloc double[] { 1.2, 5.6 };

public class Widget<T>
{
    public static implicit operator Widget<T>(Span<double> sp) { return null; }
}

V případě span8stackalloc za následek Span<int>, který je převeden implicitním operátorem na ReadOnlySpan<int>. Podobně u span9se výsledná Span<double> převede na uživatelem definovaný typ Widget<double> pomocí převodu, jak je znázorněno. konec příkladu

12.8.23 Operátor nameof

nameof_expression se používá pro získání názvu programové entity jako konstantního řetězce.

nameof_expression
    : 'nameof' '(' named_entity ')'
    ;
    
named_entity
    : named_entity_target ('.' identifier type_argument_list?)*
    ;
    
named_entity_target
    : simple_name
    | 'this'
    | 'base'
    | predefined_type 
    | qualified_alias_member
    ;

Protože nameof není klíčové slovo, nameof_expression je vždy syntakticky nejednoznačné s voláním jednoduchého jména nameof. Z důvodu kompatibility, pokud je vyhledání názvu (§12.8.4) jména nameof úspěšné, je výraz považován za invocation_expression – bez ohledu na to, zda je vyvolání platné. Jinak je to nameof_expression.

Na named_entity v době kompilace se provádějí jednoduché vyhledávání a přístup členů podle pravidel popsaných v §12.8.4 a §12.8.7. Pokud však vyhledávání popsané v §12.8.4 a §12.8.7 vede k chybě, protože člen instance byl nalezen ve statickém kontextu, nameof_expression taková chyba nevyvolá.

Jedná se o chybu v době kompilace pro named_entity označující skupinu metod tak, aby měla type_argument_list. Je to chyba v době kompilace, když named_entity_target má typ dynamic.

nameof_expression je konstantní výraz typu stringa nemá žádný účinek za běhu. Konkrétně se jeho named_entity nevyhodnocuje a je ignorována pro účely analýzy jednoznačného přiřazení (§9.4.4.22). Jeho hodnota je poslední identifikátor named_entity před volitelným konečným seznamem typových argumentů , transformována následujícím způsobem:

  • Předpona "@", pokud se používá, je odebrána.
  • Každý unicode_escape_sequence se transformuje na odpovídající znak Unicode.
  • Odeberou se všechny formátovací znaky.

Jedná se o stejné transformace použité ve §6.4.3 při testování rovnosti mezi identifikátory.

Příklad: Následující ilustruje výsledky různých výrazů nameof za předpokladu, že je deklarován obecný typ List<T> v rámci oboru názvů System.Collections.Generic:

using TestAlias = System.String;

class Program
{
    static void Main()
    {
        var point = (x: 3, y: 4);

        string n1 = nameof(System);                      // "System"
        string n2 = nameof(System.Collections.Generic);  // "Generic"
        string n3 = nameof(point);                       // "point"
        string n4 = nameof(point.x);                     // "x"
        string n5 = nameof(Program);                     // "Program"
        string n6 = nameof(System.Int32);                // "Int32"
        string n7 = nameof(TestAlias);                   // "TestAlias"
        string n8 = nameof(List<int>);                   // "List"
        string n9 = nameof(Program.InstanceMethod);      // "InstanceMethod"
        string n10 = nameof(Program.GenericMethod);      // "GenericMethod"
        string n11 = nameof(Program.NestedClass);        // "NestedClass"

        // Invalid
        // string x1 = nameof(List<>);            // Empty type argument list
        // string x2 = nameof(List<T>);           // T is not in scope
        // string x3 = nameof(GenericMethod<>);   // Empty type argument list
        // string x4 = nameof(GenericMethod<T>);  // T is not in scope
        // string x5 = nameof(int);               // Keywords not permitted
        // Type arguments not permitted for method group
        // string x6 = nameof(GenericMethod<Program>);
    }

    void InstanceMethod() { }

    void GenericMethod<T>()
    {
        string n1 = nameof(List<T>); // "List"
        string n2 = nameof(T);       // "T"
    }

    class NestedClass { }
}

Potenciálně překvapivými částmi tohoto příkladu jsou rozlišení nameof(System.Collections.Generic) na pouze "Generic" místo úplného oboru názvů, a rozlišení nameof(TestAlias) na "TestAlias" místo "String". koncový příklad

12.8.24 Výrazy anonymní metody

anonymous_method_expression je jeden ze dvou způsobů, jak definovat anonymní funkci. Dále jsou popsány v §12.19.

12.9 Unární operátory

12.9.1 Obecné

+, -, ! (logická negace pouze §12.9.4), ~, ++, --, přetypování a await jsou nazývány unárními operátory.

Poznámka: Operátor null-odpustit (§12.8.9), !, je z výše uvedeného seznamu vyloučen kvůli své povaze, která je pouze pro kompilaci a nelze ji přetížit. koncová poznámka

unary_expression
    : primary_expression
    | '+' unary_expression
    | '-' unary_expression
    | logical_negation_operator unary_expression
    | '~' unary_expression
    | pre_increment_expression
    | pre_decrement_expression
    | cast_expression
    | await_expression
    | pointer_indirection_expression    // unsafe code support
    | addressof_expression              // unsafe code support
    ;

pointer_indirection_expression (§ 23.6.2) a addressof_expression (§23.6.5) jsou k dispozici pouze v nebezpečném kódu (§23).

Je-li operand unary_expression v době kompilace typu dynamic, je dynamicky vázán (§12.3.3). V tomto případě je typ kompilace unary_expressiondynamica řešení popsané níže proběhne za běhu pomocí typu běhu operandu.

12.9.2 Unární operátor plus

Pro operaci ve tvaru +xse použije přetížení unárního operátoru (§12.4.4) pro výběr konkrétní implementace operátoru. Operand je převeden na typ parametru vybraného operátoru a typ výsledku je návratový typ operátoru. Unární operátory plus jsou předdefinované:

int operator +(int x);
uint operator +(uint x);
long operator +(long x);
ulong operator +(ulong x);
float operator +(float x);
double operator +(double x);
decimal operator +(decimal x);

Pro každý z těchto operátorů je výsledkem jednoduše hodnota operandu.

Zvedané (§12.4.8) formy předdefinovaných předdefinovaných unárních operátorů plus definovaných výše jsou také předdefinovány.

12.9.3 Unární operátor minus

Pro operaci tvaru –xse použije přetížení unárního operátoru (§12.4.4) k výběru konkrétní implementace operátoru. Operand je převeden na typ parametru vybraného operátoru a typ výsledku je návratový typ operátoru. Předdefinované unární minusové operátory jsou:

  • Celočíselná negace:

    int operator –(int x);
    long operator –(long x);
    

    Výsledek se vypočítá odečtením X od nuly. Je-li hodnota X nejmenší reprezentovatelnou hodnotou typu operandu (−2³¹ pro int nebo −2⁶³ pro long), není matematická negace X reprezentovatelná v rámci typu operandu. Pokud k tomu dojde v kontextu checked, vyvolá se System.OverflowException; pokud dojde v unchecked kontextu, je výsledkem hodnota operandu a přetečení není hlášeno.

    Je-li operand operátoru negace typu uint, je převeden na typ longa typ výsledku je long. Výjimkou je pravidlo, které povoluje zápis hodnoty int−2147483648 (−2³¹) jako celočíselná literál (§6.4.5.3).

    Pokud je operand operátoru negace typu ulong, dojde k chybě v době kompilace. Výjimkou je pravidlo, které povoluje zápis hodnoty long−9223372036854775808 (−2⁶³) jako celočíselné literály (§6.4.5.3)

  • Negace plovoucí desetinné čárky:

    float operator –(float x);
    double operator –(double x);
    

    Výsledkem je hodnota X s invertovaným znakem. Pokud je xNaN, výsledek je také NaN.

  • Desetinná negace:

    decimal operator –(decimal x);
    

    Výsledek se vypočítá odečtením X od nuly. Desetinná negace je ekvivalentní použití unárního operátoru minus typu System.Decimal.

Liftované (§12.4.8) tvary ne-liftovaných předdefinovaných unárních operátorů minus definovaných výše jsou také předdefinovány.

12.9.4 Logický operátor negace

Pro operaci typu !xse přetížení unárního operátoru (§12.4.4) použije k výběru specifické implementace operátoru. Operand je převeden na typ parametru vybraného operátoru a typ výsledku je návratový typ operátoru. Existuje pouze jeden předdefinovaný logický operátor negace:

bool operator !(bool x);

Tento operátor vypočítá logickou negaci operandu: Pokud je operand true, výsledek je false. Pokud je operand false, výsledek je true.

Pozvednuté (§12.4.8) formy neliftovaných předdefinovaných logických operátorů negace uvedených výše jsou také předdefinovány.

Poznámka: Operátory prefixového logického záporu a postfixového null-odpouštějícího (§12.8.9), přestože jsou reprezentovány stejným lexikálním znakem (!), jsou odlišné. koncová poznámka

12.9.5 Operátor bitového doplňku

Pro operaci tvaru ~xse použije rozlišení přetíženého unárního operátoru (§12.4.4) k výběru konkrétní implementace operátoru. Operand je převeden na typ parametru vybraného operátoru a typ výsledku je návratový typ operátoru. Předdefinované bitové doplňkové operátory jsou:

int operator ~(int x);
uint operator ~(uint x);
long operator ~(long x);
ulong operator ~(ulong x);

Výsledkem operace pro každý z těchto operátorů je bitový doplněk x.

Každý typ výčtu E implicitně poskytuje následující operátor bitového doplňku:

E operator ~(E x);

Výsledek vyhodnocení ~x, kde X je výraz typu výčtu E s podkladovým typem U, je naprosto stejný jako vyhodnocení (E)(~(U)x), s tím rozdílem, že převod na E je vždy proveden jako v kontextu unchecked (§12.8.20).

Pozvedané (§12.4.8) formy od nepozvednutých předdefinovaných bitových doplňkových operátorů definovaných výše jsou také předdefinovány.

12.9.6 Operátory inkrementace a dekrementace předpon

pre_increment_expression
    : '++' unary_expression
    ;

pre_decrement_expression
    : '--' unary_expression
    ;

Operand operace s předponovým přírůstkem či dekrementací musí být výraz klasifikovaný jako proměnná, přístup k vlastnosti nebo přístup indexeru. Výsledek operace je hodnota stejného typu jako operand.

Pokud je operandem operace předřazené inkrementace nebo dekrementace vlastnost nebo přístup indexeru, pak musí mít vlastnost nebo indexer přístup get i přístup set. Pokud tomu tak není, dojde k chybě doby vazby.

Rozlišení přetížení unárního operátoru (§12.4.4) se použije k výběru konkrétní implementace operátoru. Předdefinované operátory ++ a -- existují pro následující typy: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimala libovolný typ výčtu. Předdefinované operátory ++ vrátí hodnotu vytvořenou přidáním 1 do operandu a předdefinované operátory -- vrátí hodnotu vytvořenou odečtením 1 z operandu. V kontextu checked je-li výsledek tohoto sčítání nebo odčítání mimo rozsah typu výsledku a typ výsledku je celočíselný typ nebo výčtový typ, je vyvolán System.OverflowException.

Existuje implicitní převod z návratového typu vybraného unárního operátoru na typ unary_expression, jinak dojde k chybě v době kompilace.

Zpracování za běhu operace předpony přírůstku nebo dekrementace formuláře ++x nebo --x se skládá z následujících kroků:

  • Pokud se x klasifikuje jako proměnná:
    • x se vyhodnotí tak, aby vznikla proměnná.
    • Hodnota x je převedena na typ operandu vybraného operátoru a operátor je vyvolán s touto hodnotou jako argument.
    • Hodnota vrácená operátorem je převedena na typ x. Výsledná hodnota je uložena v umístění určeném vyhodnocením x.
    • a stává se výsledkem operace.
  • Pokud je x klasifikováno jako vlastnost nebo přístup indexeru:
    • Výraz instance (pokud x není static) a seznam argumentů (pokud x je přístup k indexeru) přidružený k x se vyhodnotí a výsledky se použijí v následných vyvolání get a set.
    • Vyvolá se přístupový objekt get x.
    • Hodnota vrácená akcesorem get je převedena na typ operandu vybraného operátoru a operátor je vyvolán s touto hodnotou jako argument.
    • Hodnota vrácená operátorem je převedena na typ x. Nastavovací přístup x je volán s touto hodnotou jako argumentem.
    • Tato hodnota se také stane výsledkem operace.

Operátory ++ a -- podporují také postfixovou notaci (§12.8.16). Výsledkem x++ nebo x-- je hodnota x před operací, zatímco výsledek ++x nebo --x je hodnota x po operaci. V obou případech má x stejnou hodnotu po operaci.

Operátor ++ nebo implementace operátoru -- může být vyvolána použitím buď postfixové, nebo prefixové notace. Pro tyto dva zápisy není možné mít samostatné implementace operátorů.

Zdvižené (§12.4.8) tvary u nezdvižených předem definovaných operátorů předpony inkrementace a dekrementace definovaných výše jsou také předurčeny.

12.9.7 Přetypování výrazů

cast_expression se používá k explicitnímu převodu výrazu na daný typ.

cast_expression
    : '(' type ')' unary_expression
    ;

cast_expression ve formě (T)E, kde T je typ a E je unary_expression, provede explicitní převod (§10.3) hodnoty E na typ T. Pokud neexistuje žádný explicitní převod z E na T, dojde k chybě doby vazby. V opačném případě je výsledkem hodnota vytvořená explicitním převodem. Výsledek je vždy klasifikován jako hodnota, i když E označuje proměnnou.

Gramatika cast_expression vede k určitým syntaktickým nejednoznačnostem.

Příklad: Výraz (x)–y lze interpretovat buď jako přetypovací výraz (přetypování –y k typu x), nebo jako sčítací výraz v kombinaci s výrazem v závorkách (který vypočítá hodnotu x – y). koncového příkladu

K vyřešení cast_expression nejednoznačností existuje následující pravidlo: Posloupnost jednoho nebo více tokenů (§6.4) uzavřených v závorkách se považuje za začátek cast_expression pouze v případě, že platí alespoň jedna z následujících hodnot:

  • Posloupnost tokenů je správná gramatika pro typ, ale ne pro výraz.
  • Posloupnost tokenů je správná gramatika pro typ a token bezprostředně za pravou závorkou je token "~", token "!", token "(", identifikátor (§6.4.3), literál (§6.4.5) nebo jakékoli klíčové slovo (§6.4.4) s výjimkou as a is.

Termín "správná gramatika" výše znamená pouze to, že posloupnost tokenů odpovídá konkrétní gramatické produkci. Konkrétně nebere v úvahu skutečný význam žádných identifikátorů prvků.

příklad: Pokud x a y jsou identifikátory, x.y je pro typ správná gramatika, i když x.y ve skutečnosti neoznačuje typ. konec příkladu

Poznámka: Z pravidla nejednoznačnosti platí, že pokud x a y jsou identifikátory, (x)y, (x)(y)a (x)(-y) jsou cast_expressions, ale (x)-y není, i když x identifikuje typ. Pokud je však x klíčovým slovem, které identifikuje předdefinovaný typ (například int), jsou všechny čtyři formuláře cast_expressions (protože takové klíčové slovo by pravděpodobně nemohlo být výraz sám). koncová poznámka

12.9.8 Výrazy Await

12.9.8.1 Obecné

Operátor await slouží k pozastavení vyhodnocení ohraničující asynchronní funkce, dokud se nedokončí asynchronní operace reprezentovaná operandem.

await_expression
    : 'await' unary_expression
    ;

await_expression je povolena pouze v těle asynchronní funkce (§15.15). V nejbližší obalující asynchronní funkci se await_expression nesmí vyskytovat na těchto místech:

  • Uvnitř vnořené (nesynchronní) anonymní funkce
  • Uvnitř bloku lock_statement
  • Při převodu anonymní funkce na typ výrazového stromu (§10.7.3)
  • V nebezpečném kontextu

Poznámka: await_expression se nemůže vyskytovat na většině míst v rámci query_expression, protože jsou syntakticky transformovány tak, aby používaly neasynchronní lambda výrazy. koncová poznámka

Uvnitř asynchronní funkce se await nepoužije jako available_identifier, i když lze použít doslovný identifikátor @await. Neexistuje proto syntaktická nejednoznačnost mezi await_expressions a různými výrazy zahrnujícími identifikátory. Mimo asynchronní funkce await funguje jako normální identifikátor.

Operand await_expression se nazývá úkol. Představuje asynchronní operaci, která může nebo nemusí být dokončena v okamžiku vyhodnocení await_expression. Účelem operátoru await je pozastavit provádění ohraničující asynchronní funkce, dokud není dokončena očekávaná úloha, a pak získat její výsledek.

12.9.8.2 Čekatelné výrazy

Úkol await_expression musí být čekaný. Výraz t lze očekávat, pokud platí jedna z následujících podmínek:

  • t je typ kompilace dynamic
  • t má přístupnou metodu instance nebo rozšíření nazvanou GetAwaiter, která nemá žádné parametry ani typové parametry, a návratový typ A, pro který platí všechny následující podmínky:
    • A implementuje System.Runtime.CompilerServices.INotifyCompletion rozhraní (dále označované jako INotifyCompletion pro stručnost).
    • A má přístupnou, čitelnou vlastnost instance IsCompleted typu bool
    • A má přístupnou metodu instance GetResult bez parametrů a parametrů typu

Účelem metody GetAwaiter je získat awaiter pro úkol. Typ A se nazývá typem awaiteru pro výraz await.

Účelem vlastnosti IsCompleted je určit, zda je úkol již dokončen. Pokud ano, není nutné pozastavit hodnocení.

Účelem metody INotifyCompletion.OnCompleted je registrace "pokračování" úkolu; tj. delegát (typu System.Action), který bude vyvolán po dokončení úkolu.

Účelem GetResult metody je získat výsledek úkolu po jeho dokončení. Tento výsledek může být úspěšné dokončení, případně s hodnotou výsledku, nebo může být výjimkou, která je vyvolána metodou GetResult.

12.9.8.3 Klasifikace výrazů await

Výraz await t je klasifikován stejným způsobem jako výraz (t).GetAwaiter().GetResult(). Pokud je tedy návratový typ GetResultvoid, await_expression se klasifikuje jako žádný. Pokud má Tnávratový typ, který nenívoid , await_expression se klasifikuje jako hodnota typu T.

12.9.8.4 Vyhodnocení výrazů await za běhu

Za běhu se výraz await t vyhodnotí takto:

  • Awaiter a je získán vyhodnocením výrazu (t).GetAwaiter().
  • bool b se získá vyhodnocením výrazu (a).IsCompleted.
  • Pokud je bfalse, vyhodnocení závisí na tom, jestli a implementuje rozhraní System.Runtime.CompilerServices.ICriticalNotifyCompletion (dále jen ICriticalNotifyCompletion pro stručnost). Tato kontrola se provádí v době vazby; tj. za běhu, pokud má a typ v době kompilace dynamic, a jinak při kompilaci. Nechť r označuje delegáta obnovení (§ 15.15):
    • Pokud a neimplementuje ICriticalNotifyCompletion, vyhodnotí se výraz ((a) as INotifyCompletion).OnCompleted(r).
    • Pokud a implementuje ICriticalNotifyCompletion, vyhodnotí se ((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r).
    • Vyhodnocení se pak pozastaví a řízení se vrátí k aktuálnímu volajícímu asynchronní funkce.
  • Buď ihned po (pokud b byl true), nebo po pozdějším vyvolání delegáta obnovení (pokud b byl false), vyhodnotí se výraz (a).GetResult(). Pokud vrátí hodnotu, je tato hodnota výsledkem await_expression. Jinak výsledek není žádný.

Implementace metod rozhraní INotifyCompletion.OnCompleted a ICriticalNotifyCompletion.UnsafeOnCompleted by měla způsobit vyvolání delegáta r nejvýše jednou. V opačném případě je chování nadřazené asynchronní funkce nedefinované.

12.10 Aritmetické operátory

12.10.1 Obecné

Operátory *, /, %, +a - se nazývají aritmetické operátory.

multiplicative_expression
    : unary_expression
    | multiplicative_expression '*' unary_expression
    | multiplicative_expression '/' unary_expression
    | multiplicative_expression '%' unary_expression
    ;

additive_expression
    : multiplicative_expression
    | additive_expression '+' multiplicative_expression
    | additive_expression '-' multiplicative_expression
    ;

Pokud má operand aritmetického operátoru typ kompilace dynamic, je výraz dynamicky vázán (§12.3.3). V tomto případě je typ kompilátoru výrazu dynamica níže popsané řešení proběhne za běhu pomocí typu běhu těchto operandů, které mají typ kompilace dynamic.

12.10.2 Operátor násobení

Pro operaci ve tvaru x * yse rozlišení přetížení binárního operátoru (§12.4.5) používá k určení specifické implementace operátoru. Operandy jsou převedeny na typy parametrů vybraného operátoru a typ výsledku je návratový typ operátoru.

Předdefinované operátory násobení jsou uvedeny níže. Všechny operátory vypočítají součin mezi x a y.

  • Celočíselné násobení:

    int operator *(int x, int y);
    uint operator *(uint x, uint y);
    long operator *(long x, long y);
    ulong operator *(ulong x, ulong y);
    

    V kontextu checked, pokud je produkt mimo rozsah typu výsledku, je vyvolána System.OverflowException. V kontextu unchecked nejsou přetečení hlášena a žádné významné bity s vysokým pořadím mimo rozsah typu výsledku jsou zahozena.

  • Násobení s plovoucí desetinnou čárkou:

    float operator *(float x, float y);
    double operator *(double x, double y);
    

    Produkt se vypočítá podle pravidel aritmetik IEC 60559. Následující tabulka uvádí výsledky všech možných kombinací nenulových konečných hodnot, nul, infinit a sítí NAN. V tabulce jsou x a y kladné konečné hodnoty. z je výsledkem x * y, zaokrouhleno na nejbližší reprezentovatelnou hodnotu. Pokud je velikost výsledku pro cílový typ příliš velká, z je nekonečno. Vzhledem k zaokrouhlování může být z nula, i když x ani y není nula.

    +y -y +0 -0 +∞ -∞ NaN
    +x +z -z +0 -0 +∞ -∞ NaN
    -x -z +z -0 +0 -∞ +∞ NaN
    +0 +0 -0 +0 -0 NaN NaN NaN
    -0 -0 +0 -0 +0 NaN NaN NaN
    +∞ +∞ -∞ NaN NaN +∞ -∞ NaN
    -∞ -∞ +∞ NaN NaN -∞ +∞ NaN
    NaN NaN NaN NaN NaN NaN NaN NaN

    (S výjimkou případů, kde je uvedeno jinak, v tabulkách s plovoucí čárkou v §12.10.2§12.10.6 použití "+" znamená, že hodnota je kladná; použití "-" znamená, že hodnota je záporná; a absence znaménka znamená, že hodnota může být kladná nebo záporná, nebo nemá znaménko (NaN).)

  • Násobení desetinných čísel

    decimal operator *(decimal x, decimal y);
    

    Pokud je velikost výsledné hodnoty příliš velká na to, aby ji bylo možné reprezentovat v desítkovém formátu, je generována chyba System.OverflowException. Vzhledem k zaokrouhlování může být výsledek nula, i když žádný operand není nula. Měřítko výsledku před zaokrouhlováním je součet měřítka dvou operandů. Násobení desetinných čísel je ekvivalentní použití operátoru násobení typu System.Decimal.

Zvedané (§12.4.8) tvary nezvednutých předdefinovaných operátorů násobení definovaných výše jsou také předdefinovány.

12.10.3 Operátor dělení

Pro operaci ve formě x / yse použije rozlišení přetížení binárního operátoru (§12.4.5) k výběru konkrétní implementace operátoru. Operandy jsou převedeny na typy parametrů vybraného operátoru a typ výsledku je návratový typ operátoru.

Níže jsou uvedeny předdefinované operátory dělení. Všichni operátoři vypočítají podíl mezi x a y.

  • Celočíselné dělení:

    int operator /(int x, int y);
    uint operator /(uint x, uint y);
    long operator /(long x, long y);
    ulong operator /(ulong x, ulong y);
    

    Pokud je hodnota pravého operandu nula, vyvolá se System.DivideByZeroException.

    Dělení zaokrouhlí výsledek na nulu. Absolutní hodnota výsledku je tedy největší možné celé číslo, které je menší nebo rovno absolutní hodnotě podílu obou operandů. Výsledek je nulový nebo kladný, pokud dva operandy mají stejné znaménko a nula nebo záporné, pokud mají dva operandy opačné znaménka.

    Pokud je levý operand nejmenší hodnota, kterou lze reprezentovat jako int nebo long, a pravý operand je –1, dojde k přetečení. V kontextu checked to způsobí vyvolání System.ArithmeticException (nebo jeho podtřídy). V kontextu unchecked záleží na implementaci, zda je vyvolána výjimka System.ArithmeticException (nebo její podtřída), nebo zda je přetečení ignorováno a výsledná hodnota odpovídá levému operandu.

  • Dělení s plovoucí desetinnou čárkou:

    float operator /(float x, float y);
    double operator /(double x, double y);
    

    Podíl se vypočítá podle pravidel aritmetiky IEC 60559. Následující tabulka uvádí výsledky všech možných kombinací nenulových konečných hodnot, nul, infinit a sítí NAN. V tabulce jsou x a y kladné konečné hodnoty. z je výsledkem x / y, zaokrouhleno na nejbližší reprezentovatelnou hodnotu.

    +y -y +0 -0 +∞ -∞ NaN
    +x +z -z +∞ -∞ +0 -0 NaN
    -x -z +z -∞ +∞ -0 +0 NaN
    +0 +0 -0 NaN NaN +0 -0 NaN
    -0 -0 +0 NaN NaN -0 +0 NaN
    +∞ +∞ -∞ +∞ -∞ NaN NaN NaN
    -∞ -∞ +∞ -∞ +∞ NaN NaN NaN
    NaN NaN NaN NaN NaN NaN NaN NaN
  • Dělení desetinných čísel

    decimal operator /(decimal x, decimal y);
    

    Pokud je hodnota pravého operandu nula, vyvolá se System.DivideByZeroException. Pokud je absolutní hodnota výsledné hodnoty příliš velká, aby mohla být vyjádřena v desítkovém formátu, je generována System.OverflowException. Vzhledem k zaokrouhlování může být výsledek nula, i když první operand není nula. Měřítko výsledku před zaokrouhlováním je nejblíže k upřednostňované škále, která zachová výsledek, který se rovná přesnému výsledku. Upřednostňované měřítko je měřítko x s odečtením měřítka y.

    Desetinné dělení je ekvivalentní použití operátoru dělení typu System.Decimal.

Zdvižené (§12.4.8) formy nezdvižených předdefinovaných operátorů dělení definovaných výše jsou také předdefinovány.

12.10.4 Operátor zbytku

Pro operaci tvaru x % yse rozlišení přetížení binárního operátoru (§12.4.5) použije, aby se vybrala konkrétní implementace operátoru. Operandy jsou převedeny na typy parametrů vybraného operátoru a typ výsledku je návratový typ operátoru.

Předdefinované operátory zbytku jsou uvedeny níže. Všechny operátory vypočítají zbytek dělení mezi x a y.

  • Zbytek po dělení celými čísly:

    int operator %(int x, int y);
    uint operator %(uint x, uint y);
    long operator %(long x, long y);
    ulong operator %(ulong x, ulong y);
    

    Výsledkem x % y je hodnota vytvořená x – (x / y) * y. Pokud je y nula, vyvolá se System.DivideByZeroException.

    Pokud je levý operand nejmenší int nebo long a pravý operand je –1, vyvolá se System.OverflowException, pouze pokud x / y vyvolá výjimku.

  • Zbytek čísla s plovoucí řádovou čárkou:

    float operator %(float x, float y);
    double operator %(double x, double y);
    

    Následující tabulka uvádí výsledky všechny možné kombinace nenulových konečných hodnot, nul, nekonečna a NaN. V tabulce jsou x a y kladné konečné hodnoty. z je výsledkem x % y a vypočítá se jako x – n * y, kde n je největší možné celé číslo, které je menší nebo rovno x / y. Tato metoda výpočtu zbytku je podobná té, která se používá pro celočíselné operandy, ale liší se od definice IEC 60559 (ve které je n celé číslo nejblíže x / y).

    +y -y +0 -0 +∞ -∞ NaN
    +x +z +z NaN NaN +x +x NaN
    -x -z -z NaN NaN -x -x NaN
    +0 +0 +0 NaN NaN +0 +0 NaN
    -0 -0 -0 NaN NaN -0 -0 NaN
    +∞ NaN NaN NaN NaN NaN NaN NaN
    -∞ NaN NaN NaN NaN NaN NaN NaN
    NaN NaN NaN NaN NaN NaN NaN NaN
  • Desetinný zbytek:

    decimal operator %(decimal x, decimal y);
    

    Pokud je hodnota pravého operandu nula, vyvolá se System.DivideByZeroException. Je určeno implementací, kdy je vyvolána System.ArithmeticException (nebo její podtřída). Odpovídající implementace nevyvolá výjimku pro x % y v každém případě, kdy x / y nevyvolá výjimku. Škála výsledku před zaokrouhlením je větší z měřítek dvou operandů a znamení výsledku, pokud není nula, je stejné jako u x.

    Desetinný zbytek je ekvivalentní použití operátoru zbytku typu System.Decimal.

    Poznámka: Tato pravidla zajišťují, aby výsledek pro všechny typy nikdy neměl opačné znaménko levého operandu. koncová poznámka

Zvednuté (§12.4.8) verze výše definovaných předdefinovaných zbytkových operátorů jsou také předdefinovány.

12.10.5 Operátor sčítání

Pro operaci ve tvaru x + yse použije rozlišení přetížení binárního operátoru (§12.4.5) pro výběr konkrétní implementace operátoru. Operandy jsou převedeny na typy parametrů vybraného operátoru a typ výsledku je návratový typ operátoru.

Předdefinované operátory sčítání jsou uvedeny níže. Předdefinované operátory sčítání u číselných a výčtových typů vypočítávají součet dvou operandů. Pokud je jeden nebo oba operandy typu string, předdefinované operátory sčítání zřetězí jejich řetězcovou reprezentaci.

  • Celočíselné sčítání:

    int operator +(int x, int y);
    uint operator +(uint x, uint y);
    long operator +(long x, long y);
    ulong operator +(ulong x, ulong y
    

    V kontextu checked, pokud je součet mimo rozsah typu výsledku, je vyvolán System.OverflowException. V kontextu unchecked nejsou přetečení hlášena a významné vysokořádové bity mimo rozsah typu výsledku jsou zahazovány.

  • Sčítání s plovoucí řádovou čárkou

    float operator +(float x, float y);
    double operator +(double x, double y);
    

    Součet se vypočítá podle pravidel aritmetik IEC 60559. Následující tabulka uvádí výsledky všech možných kombinací nenulových konečných hodnot, nul, nekonečen a NaN. V tabulce jsou x a y nenulové konečné hodnoty a z je výsledkem x + y. Pokud x a y mají stejnou velikost, ale opačné znaky, z je kladná nula. Pokud je x + y příliš velký na zobrazení v cílovém typu, z je nekonečno se stejným znaménkem jako x + y.

    y +0 -0 +∞ -∞ NaN
    x z x x +∞ -∞ NaN
    +0 y +0 +0 +∞ –∞ NaN
    -0 y +0 -0 +∞ -∞ NaN
    +∞ +∞ +∞ +∞ +∞ NaN NaN
    -∞ -∞ -∞ -∞ NaN -∞ NaN
    NaN NaN NaN NaN NaN NaN NaN
  • Sčítání desetinných čísel

    decimal operator +(decimal x, decimal y);
    

    Pokud je velikost výsledné hodnoty příliš velká na to, aby měla reprezentaci v desítkovém formátu, je vyvolána chyba System.OverflowException. Měřítko výsledku před zaokrouhlováním je větší z měřítek dvou operandů.

    Desetinné sčítání je ekvivalentní použitím operátoru sčítání typu System.Decimal.

  • Přidávání k výčtu. Každý typ výčtu implicitně poskytuje následující předdefinované operátory, kde E je typ výčtu a U je základní typ E:

    E operator +(E x, U y);
    E operator +(U x, E y);
    

    Během běhu se tyto operátory vyhodnocují přesně způsobem jako (E)((U)x + (U)y).

  • Zřetězení řetězců:

    string operator +(string x, string y);
    string operator +(string x, object y);
    string operator +(object x, string y);
    

    Tato přetížení binárního operátoru + provádí zřetězení řetězců. Pokud je operand zřetězení řetězce null, nahradí se prázdný řetězec. V opačném případě se jakýkoli operand, který nenístring, převede na řetězcovou reprezentaci vyvoláním virtuální ToString metody zděděné z typu object. Pokud ToString vrátí null, nahradí se prázdný řetězec.

    Příklad :

    class Test
    {
        static void Main()
        {
            string s = null;
            Console.WriteLine("s = >" + s + "<");  // Displays s = ><
    
            int i = 1;
            Console.WriteLine("i = " + i);         // Displays i = 1
    
            float f = 1.2300E+15F;
            Console.WriteLine("f = " + f);         // Displays f = 1.23E+15
    
            decimal d = 2.900m;
            Console.WriteLine("d = " + d);         // Displays d = 2.900
       }
    }
    

    Výstup zobrazený v komentářích je typickým výsledkem US-English systému. Přesný výstup může záviset na místním nastavení spouštěcího prostředí. Operátor zřetězení řetězců se v každém případě chová stejným způsobem, ale místní nastavení můžou mít vliv na metody ToString implicitně volané během provádění.

    konec příkladu

    Výsledkem operátoru zřetězení řetězce je string, který se skládá ze znaků levého operandu, po kterých následují znaky pravého operandu. Operátor zřetězení řetězců nikdy nevrátí hodnotu null. System.OutOfMemoryException může být vyhozena, pokud není k dispozici dostatek paměti pro přidělení výsledného řetězce.

  • Kombinace delegátů Každý typ delegáta implicitně poskytuje následující předdefinovaný operátor, kde D je typ delegáta:

    D operator +(D x, D y);
    

    Pokud je první operand null, je výsledkem operace hodnota druhého operandu (i když je to také null). Jinak, pokud je druhý operand null, je výsledkem operace hodnota prvního operandu. V opačném případě je výsledkem operace nová instance delegáta, jejíž seznam vyvolání se skládá z prvků v seznamu vyvolání prvního operandu, následované prvky v seznamu vyvolání druhého operandu. To znamená, že seznam vyvolání výsledného delegáta je spojením seznamů vyvolání dvou operandů.

    Poznámka: Příklady kombinace delegátů naleznete v §12.10.6 a §20.6. Vzhledem k tomu, že System.Delegate není typu delegáta, operátor + není pro něj definován. koncová poznámka

Zvedané (§12.4.8) tvary nezdvižených předdefinovaných operátorů sčítání definované výše jsou také předdefinovány.

12.10.6 Operátor odčítání

Pro operaci tvaru x – yje použita přetížení binárního operátoru (§12.4.5) k výběru konkrétní implementace operátoru. Operandy jsou převedeny na typy parametrů vybraného operátoru a typ výsledku je návratový typ operátoru.

Předdefinované operátory odčítání jsou uvedeny níže. Všechny operátory odečítají y od x.

  • Odčítání celého čísla:

    int operator –(int x, int y);
    uint operator –(uint x, uint y);
    long operator –(long x, long y);
    ulong operator –(ulong x, ulong y
    

    Pokud je v kontextu checked rozdíl mimo rozsah typu výsledku, vyvolá se System.OverflowException. V kontextu unchecked nejsou přetečení hlášena a jakékoli významné bity s vysokým pořadím mimo rozsah typu výsledku jsou zahozena.

  • Odčítání s plovoucí desetinou čárkou:

    float operator –(float x, float y);
    double operator –(double x, double y);
    

    Rozdíl se vypočítá podle pravidel aritmetik IEC 60559. Následující tabulka uvádí výsledky všech možných kombinací nenulových konečných hodnot, nul, nekonečen a NaN. V tabulce jsou x a y nenulové konečné hodnoty a z je výsledkem x – y. Pokud jsou x a y stejné, z je kladná nula. Pokud je x – y příliš velký na reprezentaci v cílovém typu, z je nekonečno se stejným znaménkem jako x – y.

    y +0 -0 +∞ -∞ NaN
    x z x x -∞ +∞ NaN
    +0 -y +0 +0 -∞ +∞ NaN
    -0 -y -0 +0 -∞ +∞ NaN
    +∞ +∞ +∞ +∞ NaN +∞ NaN
    -∞ -∞ -∞ -∞ -∞ NaN NaN
    NaN NaN NaN NaN NaN NaN NaN

    (Ve výše uvedené tabulce položky -y označují negaci u y, nikoli že jde o zápornou hodnotu.)

  • Odčítání desetinných čísel:

    decimal operator –(decimal x, decimal y);
    

    Pokud je velikost výsledné hodnoty příliš velká, aby mohla být vyjádřena v desítkovém formátu, vyvolá se System.OverflowException. Měřítko výsledku před zaokrouhlováním je větší ze dvou měřítek operandů.

    Odčítání desetinných čísel je ekvivalentní k použití operátoru odčítání typu System.Decimal.

  • Odčítání výčtu Každý typ výčtu implicitně poskytuje následující předdefinovaný operátor, kde E je typ výčtu a U je základní typ E:

    U operator –(E x, E y);
    

    Tento operátor je vyhodnocen přesně jako (U)((U)x – (U)y). Jinými slovy, operátor vypočítá rozdíl mezi pořadovými hodnotami x a ya typem výsledku je základní typ výčtu.

    E operator –(E x, U y);
    

    Tento operátor je vyhodnocen přesně jako (E)((U)x – y). Jinými slovy, operátor odečte hodnotu od základního typu výčtu a získá hodnotu výčtu.

  • Odebrání delegáta Každý typ delegáta implicitně poskytuje následující předdefinovaný operátor, kde D je typ delegáta:

    D operator –(D x, D y);
    

    Sémantika je následující:

    • Pokud je první operand null, výsledek operace je null.
    • Jinak, pokud je druhý operand null, je výsledkem operace hodnota prvního operandu.
    • V opačném případě oba operandy představují neprázdné seznamy volání (§20.2).
      • Pokud jsou seznamy shodné, jak určuje delegovaný operátor rovnosti (§12.12.9), výsledek operace je null.
      • V opačném případě je výsledkem operace nový seznam vyvolání, který se skládá ze seznamu prvního operandu s odebranými položkami druhého operandu, za předpokladu, že seznam druhého operandu je dílčí seznam prvního operandu. (Pokud chcete zjistit rovnost dílčího seznamu, porovnávají se odpovídající položky stejně jako u operátoru rovnosti delegáta.) Pokud seznam druhého operandu odpovídá více dílčím seznamům souvislých položek v seznamu prvního operandu, poslední odpovídající dílčí seznam souvislých položek je odstraněn.
      • V opačném případě je výsledkem operace hodnota levého operandu.

    Ani jeden ze seznamů operandů (pokud existuje) se v procesu nezmění.

    Příklad :

    delegate void D(int x);
    
    class C
    {
        public static void M1(int i) { ... }
        public static void M2(int i) { ... }
    }
    
    class Test
    {
        static void Main()
        {
            D cd1 = new D(C.M1);
            D cd2 = new D(C.M2);
            D list = null;
    
            list = null - cd1;                             // null
            list = (cd1 + cd2 + cd2 + cd1) - null;         // M1 + M2 + M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - cd1;          // M1 + M2 + M2
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2);  // M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd2);  // M1 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd1);  // M1 + M2
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd1);  // M1 + M2 + M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2 + cd2 + cd1);  // null
        }
    }
    

    koncového příkladu

Liftované (§12.4.8) tvary nenaliftovaných předdefinovaných operátorů odčítání definovaných výše jsou také předdefinovány.

Operátory posunu 12.11

Operátory << a >> slouží k provádění operací posunu bitů.

shift_expression
    : additive_expression
    | shift_expression '<<' additive_expression
    | shift_expression right_shift additive_expression
    ;

Pokud má operand shift_expression typ kompilace dynamic, je výraz dynamicky vázán (§12.3.3). V tomto případě je typ kompilátoru výrazu dynamica níže popsané řešení proběhne za běhu pomocí typu běhu těchto operandů, které mají typ kompilace dynamic.

Pro operaci formuláře x << count nebo x >> countse použije rozlišení přetížení binárního operátoru (§12.4.5) pro výběr konkrétní implementace operátoru. Operandy jsou převedeny na typy parametrů vybraného operátoru a typ výsledku je návratový typ operátoru.

Při deklarování přetíženého operátoru posunu musí typ prvního operandu vždy být třída či struktura obsahující deklaraci operátoru a typ druhého operandu musí být vždy int.

Předdefinované operátory posunu jsou uvedeny níže.

  • Posuňte doleva.

    int operator <<(int x, int count);
    uint operator <<(uint x, int count);
    long operator <<(long x, int count);
    ulong operator <<(ulong x, int count);
    

    Operátor << posune x doleva o počet bitů vypočítaný, jak je popsáno níže.

    Bity s vysokým pořadím mimo rozsah typu výsledku x se zahodí, zbývající bity se posunou doleva a pozice prázdných bitů s nízkým pořadím se nastaví na nulu.

  • Posunout doprava:

    int operator >>(int x, int count);
    uint operator >>(uint x, int count);
    long operator >>(long x, int count);
    ulong operator >>(ulong x, int count);
    

    Operátor >> posune x doprava o počet bitů, jak je popsáno níže.

    Pokud je x typu int nebo long, zahodí se bity x s nízkým pořadím, zbývající bity se posunou doprava a pozice prázdných bitů s vysokým pořadím se nastaví na nulu, pokud je x nezáporná a nastaví se na jeden, pokud je x záporná.

    Pokud je x typu uint nebo ulong, zahodí se bity x s nízkým pořadím, zbývající bity se posunou doprava a pozice prázdných bitů s vysokým pořadím se nastaví na nulu.

U předdefinovaných operátorů se počet bitů, které se mají posunout, vypočítá takto:

  • Pokud je typ xint nebo uint, počet posunů je dán pěti bity nižšího řádu count. Jinými slovy, počet směn se vypočítá z count & 0x1F.
  • Pokud je typ xlong nebo ulong, počet posunů je dán nízkými řádovými šesti bity count. Jinými slovy, počet směn se vypočítá z count & 0x3F.

Pokud je výsledný počet směn nula, operátory směn jednoduše vrátí hodnotu x.

Operace směny nikdy nezpůsobí přetečení a vytvoří stejné výsledky v nezaškrtnutých kontextech.

Pokud je levý operand operátoru >> znaménkový celočíselný typ, operátor provede aritmetický posun doprava, kde se hodnota nejvýznamnějšího bitu (znaménkového bitu) operandu rozšíří do volných pozic bitů s vysokým pořadím. Pokud levý operand operátoru >> je celočíselný typ bez znaménka, operátor provede logickou posunu doprava, kde jsou pozice prázdných bitů ve vysokém pořadí vždy nastaveny na nulu. Chcete-li provést opačnou operaci odvozenou z typu operandu, lze použít explicitní přetypování.

Příklad: Pokud je x proměnnou typu int, operace unchecked ((int)((uint)x >> y)) provede logický posun vpravo od x. konec příkladu

Zvedané (§12.4.8) tvary níže definovaných nepředdefinovaných operátorů posunu jsou také předdefinovány.

12.12 Relační operátory a operátory pro testování typů

12.12.1 Obecné

Operátory ==, !=, <, >, <=, >=, isa as se nazývají relační operátory a operátory pro testování typů.

relational_expression
    : shift_expression
    | relational_expression '<' shift_expression
    | relational_expression '>' shift_expression
    | relational_expression '<=' shift_expression
    | relational_expression '>=' shift_expression
    | relational_expression 'is' type
    | relational_expression 'is' pattern
    | relational_expression 'as' type
    ;

equality_expression
    : relational_expression
    | equality_expression '==' relational_expression
    | equality_expression '!=' relational_expression
    ;

Poznámka: Vyhledání správného operandu operátoru is musí nejprve být testováno jako typ, pak jako výraz , který může se rozprostírat přes více tokenů. V případě, že je operand expreesion, musí mít výraz vzoru prioritu alespoň tak vysoký jako shift_expression. koncová poznámka

Operátor is je popsán v §12.12.12 a operátor as je popsán v §12.12.13.

Operátory ==, !=, <, >, <= a >= jsou operátory porovnání.

Pokud se default_literal (§ 12.8.21) používá jako operand operátoru <, >, <=nebo >=, dojde k chybě při kompilaci. Pokud se default_literal používá jako oba operandy operátoru == nebo !=, dojde k chybě v době kompilace. Pokud se default_literal použije jako levý operand operátoru is nebo as, dojde k chybě v době kompilace.

Pokud má operand porovnávacího operátoru typ při kompilaci dynamic, je výraz dynamicky vázán (§12.3.3). V tomto případě je typ kompilace výrazu dynamica rozlišení popsané níže proběhne za běhu pomocí typu běhu těchto operandů, které mají typ kompilačního času dynamic.

Pro operaci ve tvaru x «op» y, kde «op» je porovnávací operátor, je použito přetížení rozlišení (§12.4.5) pro výběr konkrétní implementace operátoru. Operandy jsou převedeny na typy parametrů vybraného operátoru a typ výsledku je návratový typ operátoru. Pokud jsou oba operandy výrazu equality_expression literály null, pak se neprovádí rozlišení přetížení a výraz se vyhodnotí jako konstantní hodnota true nebo false, podle toho, zda je operátor == nebo !=.

Předdefinované porovnávací operátory jsou popsány v následujících pododstavcích. Všechny předdefinované srovnávací operátory vrátí výsledek typu bool, jak je uvedeno v následující tabulce.

operace výsledek
x == y true, pokud se x rovná y, false jinak
x != y true, pokud se x nerovná y, false jinak
x < y true, pokud je x menší než y, false jinak
x > y true, pokud je x větší než y, false jinak
x <= y true, pokud je x menší nebo roven y, false jinak
x >= y true, pokud je x větší než nebo roven y, false jinak

12.12.2 Celočíselné relační operátory

Předdefinované celočíselné relační operátory jsou:

bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);

bool operator !=(int x, int y);
bool operator !=(uint x, uint y);
bool operator !=(long x, long y);
bool operator !=(ulong x, ulong y);

bool operator <(int x, int y);
bool operator <(uint x, uint y);
bool operator <(long x, long y);
bool operator <(ulong x, ulong y);

bool operator >(int x, int y);
bool operator >(uint x, uint y);
bool operator >(long x, long y);
bool operator >(ulong x, ulong y);

bool operator <=(int x, int y);
bool operator <=(uint x, uint y);
bool operator <=(long x, long y);
bool operator <=(ulong x, ulong y);

bool operator >=(int x, int y);
bool operator >=(uint x, uint y);
bool operator >=(long x, long y);
bool operator >=(ulong x, ulong y);

Každý z těchto operátorů porovnává číselné hodnoty dvou celočíselných operandů a vrací hodnotu bool, která určuje, zda je konkrétní vztah true nebo false.

Lifted (§12.4.8) tvary předdefinovaných předdefinovaných integerových relačních operátorů definovaných výše jsou také předdefinovány.

12.12.3 Operátory pro porovnání s plovoucí desetinnou čárkou

Předdefinované operátory porovnání pro plovoucí desetinnou čárku jsou:

bool operator ==(float x, float y);
bool operator ==(double x, double y);

bool operator !=(float x, float y);
bool operator !=(double x, double y);

bool operator <(float x, float y);
bool operator <(double x, double y);

bool operator >(float x, float y);
bool operator >(double x, double y);

bool operator <=(float x, float y);
bool operator <=(double x, double y);

bool operator >=(float x, float y);
bool operator >=(double x, double y);

Operátory porovnávají operandy podle pravidel standardu IEC 60559:

Pokud je jeden operand NaN, výsledek je false pro všechny operátory kromě !=, pro které je výsledek true. U všech dvou operandů x != y vždy vytvoří stejný výsledek jako !(x == y). Pokud jsou však jeden nebo oba operandy NaN, operátory <, >, <=a >=nevygenerují stejné výsledky, jako logická negace oproti opačnému operátoru.

příklad: Pokud některý z x a y je NaN, x < y je false, ale !(x >= y) je true. koncového příkladu

Pokud ani jeden z operandů není NaN, operátory porovnávají hodnoty dvou operandů s plovoucím desetinným bodem s ohledem na řazení.

–∞ < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +∞

kde min a max jsou nejmenší a největší kladné konečné hodnoty, které lze reprezentovat v daném formátu s plovoucí desetinou čárkou. Mezi efekty tohoto řazení patří:

  • Záporné a kladné nuly jsou považovány za stejné.
  • Záporné nekonečno je považováno za menší než všechny ostatní hodnoty, ale rovná se jinému zápornému nekonečnu.
  • Kladné nekonečno se považuje za větší než všechny ostatní hodnoty, ale rovná se jinému kladnému nekonečnu.

Lifted (§12.4.8) formy výše definovaných nepředdefinovaných relačních operátorů s plovoucí desetinnou čárkou jsou také předdefinovány.

12.12.4 Operátory porovnání desetinných čísel

Předdefinované relační operátory desetinných míst jsou:

bool operator ==(decimal x, decimal y);
bool operator !=(decimal x, decimal y);
bool operator <(decimal x, decimal y);
bool operator >(decimal x, decimal y);
bool operator <=(decimal x, decimal y);
bool operator >=(decimal x, decimal y);

Každý z těchto operátorů porovnává číselné hodnoty dvou desetinných operandů a vrací hodnotu bool, která označuje, zda je konkrétní vztah true nebo false. Každé porovnání desetinných míst odpovídá použití odpovídajícího relačního operátoru nebo operátoru rovnosti typu System.Decimal.

Přenesené (§12.4.8) formy neprodloužených předdefinovaných desetinových porovnávacích operátorů definovaných výše jsou také předdefinovány.

12.12.5 Logické operátory rovnosti

Předdefinované logické operátory rovnosti jsou:

bool operator ==(bool x, bool y);
bool operator !=(bool x, bool y);

Výsledek == je true, pokud jsou x i ytrue nebo pokud jsou x i yfalse. V opačném případě je výsledek false.

Výsledek != je false, pokud jsou x i ytrue nebo pokud jsou x i yfalse. V opačném případě je výsledek true. Pokud jsou operandy typu bool, operátor != vytvoří stejný výsledek jako operátor ^.

Zvýšené (§12.4.8) formy nezvýšených předdefinovaných Booleovských operátorů rovnosti definovaných výše jsou také předdefinovány.

12.12.6 Porovnávací operátory výčtů

Každý typ výčtu implicitně poskytuje následující předdefinované relační operátory.

bool operator ==(E x, E y);
bool operator !=(E x, E y);

bool operator <(E x, E y);
bool operator >(E x, E y);
bool operator <=(E x, E y);
bool operator >=(E x, E y);

Výsledek vyhodnocení x «op» y, kde x a y jsou výrazy typu výčtu E s podkladovým typem U, a «op» je jedním z relačních operátorů, je přesně stejný jako vyhodnocení ((U)x) «op» ((U)y). Jinými slovy, operátory porovnání typů výčtu jednoduše porovnávají základní celočíselné hodnoty dvou operandů.

Vyzdvižené formy (§12.4.8) nevyzdvižených předdefinovaných relačních operátorů výčtu definovaných výše jsou také předdefinovány.

12.12.7 Operátory rovnosti referenčních typů

Každý typ třídy C implicitně poskytuje následující předdefinované operátory rovnosti typů odkazů:

bool operator ==(C x, C y);
bool operator !=(C x, C y);

pokud neexistují jiné předdefinované operátory rovnosti pro C (například pokud je Cstring nebo System.Delegate).

Operátory vrátí výsledek porovnání dvou referencí na rovnost nebo nerovnost. operator == vrátí true, pokud a pouze pokud x a y odkazují na stejnou instanci nebo jsou oba null, zatímco operator != vrátí true pokud a pouze pokud operator == se stejnými operandy vrátí false.

Kromě běžných pravidel použitelnosti (§ 12.6.4.2), předdefinované operátory rovnosti typů odkazů vyžadují, aby bylo možné použít jednu z následujících možností:

  • Oba operandy jsou hodnotou typu známého jako reference_type nebo literál null. Kromě toho existuje převod identity nebo explicitního odkazu (§10.3.5) z jednoho operandu na typ druhého operandu.
  • Jeden operand je literál nulla druhý operand je hodnota typu T, kde T je typový parametr, u kterého není známo, že by šlo o typ hodnoty a nemá omezení typem hodnoty.
    • Pokud je za běhu T nenulový typ hodnoty, výsledek == je false a výsledek != je true.
    • Pokud je za běhu T nulovatelný typ hodnoty, výsledek se vypočítá z vlastnosti operandu HasValue, jak je popsáno v (§12.12.10).
    • Pokud je za běhu T typ odkazu, výsledek je true, pokud je operand null, a jinak je false.

Není-li splněna některá z těchto podmínek, dojde k chybě při vázání času.

Poznámka: Důležité důsledky těchto pravidel:

  • Jedná se o chybu doby vazby při použití předdefinovaných operátorů rovnosti typu odkazu k porovnání dvou odkazů, o kterých je známo, že se v době vazby liší. Pokud jsou například typy času vazby operandů dva typy tříd a ani jeden není odvozený od toho druhého, pak by nebylo možné, aby dva operandy odkazovaly na stejný objekt. Operace je tedy považována za chybu v době vazby.
  • Předdefinované operátory rovnosti typu odkazu nepovolují porovnání operandů typu hodnot (s výjimkou případů, kdy jsou parametry typu porovnány s null, který je zpracován speciálně).
  • Operandy operátorů rovnosti pro referenční typy, které jsou předem definovány, nejsou nikdy boxovány. Nemá smysl provádět takové operace zaobalování, protože odkazy na nově přidělené zaobalené instance se nutně budou lišit od všech ostatních odkazů.

Pro operaci tvaru x == y nebo x != y, je-li k dispozici uživatelsky definovaná operator == nebo operator !=, pravidla řešení přetížení operátoru (§12.4.5) vyberou tenhle operátor namísto předdefinovaného operátoru rovnosti typu odkazu. Vždy je možné vybrat předdefinovaný operátor rovnosti pro referenční typy explicitním přetypováním jednoho nebo obou operandů na typ object.

koncová poznámka

Příklad: Následující příklad zkontroluje, zda je argument typu parametrické, který není omezený, null.

class C<T>
{
   void F(T x)
   {
      if (x == null)
      {
          throw new ArgumentNullException();
      }
      ...
   }
}

Konstrukce x == null je povolena, i když T může být nenulový typ hodnoty a výsledek je jednoduše určen jako false, když T představuje nenulový typ hodnoty.

Konec příkladu

Pro operaci ve formě x == y nebo x != y, pokud existuje jakýkoli platný operator == nebo operator !=, pravidla rozlišení přetížení operátoru (§12.4.5) vyberou tento operátor místo předdefinovaného operátoru rovnosti typu odkazu.

Poznámka: Vždy je možné vybrat předdefinovaný operátor rovnosti typu odkazu explicitním přetypováním obou operandů na typ object. koncová poznámka

Příklad: Příklad

class Test
{
    static void Main()
    {
        string s = "Test";
        string t = string.Copy(s);
        Console.WriteLine(s == t);
        Console.WriteLine((object)s == t);
        Console.WriteLine(s == (object)t);
        Console.WriteLine((object)s == (object)t);
    }
}

produkuje výstup.

True
False
False
False

Proměnné s a t odkazují na dvě různé instance řetězců obsahující stejné znaky. První porovnání dává výsledek True, protože je zvolen předdefinovaný operátor rovnosti řetězce (§12.12.8), když jsou oba operandy typu string. Zbývající porovnání všech mají výstup False, protože přetížení operator == typu string není relevantní, pokud má některý operand typ přiřazení času object.

Všimněte si, že výše uvedená technika není pro typy hodnot smysluplná. Příklad

class Test
{
    static void Main()
    {
        int i = 123;
        int j = 123;
        Console.WriteLine((object)i == (object)j);
    }
}

výstup je False, protože přetypování vytváří odkazy na dvě samostatné instance boxovaných int hodnot.

konec příkladu

12.12.8 Operátory rovnosti řetězců

Předdefinované operátory rovnosti řetězců jsou:

bool operator ==(string x, string y);
bool operator !=(string x, string y);

Dvě string hodnoty jsou považovány za stejné, pokud platí jedna z následujících hodnot:

  • Obě hodnoty jsou null.
  • Obě hodnoty jsou ne-null odkazy na instance řetězců, které mají identické délky a identické znaky v každé pozici znaku.

Operátory rovnosti řetězců porovnávají řetězcové hodnoty místo odkazů na řetězce. Pokud dvě samostatné instance řetězců obsahují přesně stejnou sekvenci znaků, hodnoty řetězců jsou stejné, ale odkazy se liší.

Poznámka: Jak je popsáno v §12.12.7, lze operátory rovnosti referenčního typu použít k porovnání řetězcových odkazů místo řetězcových hodnot. koncová poznámka

12.12.9 Delegování operátorů rovnosti

Předdefinované operátory rovnosti delegáta jsou:

bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y);

Dvě instance delegátů se považují za stejné:

  • Pokud je některá z instancí delegáta null, jsou stejné, pokud a pouze pokud jsou oba null.
  • Pokud mají delegáti jiný běhový typ, nikdy nejsou stejní.
  • Pokud mají obě instance delegáta seznam vyvolání (§20.2), jsou tyto instance stejné, pouze pokud jejich seznamy vyvolání mají stejnou délku a každá položka v seznamu vyvolání je rovna (jak je definováno níže) odpovídající položce v seznamu vyvolání druhého.

Následující pravidla určují rovnost položek seznamu volání:

  • Pokud dvě položky seznamu vyvolání oba odkazují na stejnou statickou metodu, položky jsou stejné.
  • Pokud dvě položky seznamu volání odkazují na stejnou nestatickou metodu na stejném cílovém objektu (jak je definováno operátory rovnosti referencí), položky jsou si rovny.
  • Položky seznamu vyvolání vytvořené z vyhodnocení sémanticky identických anonymních funkcí (§12.19) se stejnou (možná prázdnou) sadou zachycenými instancemi vnější proměnné mohou být (ale nemusí být) považovány za rovné.

Pokud řešení přetížení operátoru vyhodnotí některý z operátorů rovnosti delegáta a typy vazby obou operandů jsou typy delegátů, jak je popsáno v §20 místo v System.Delegate, a neexistuje žádný převod identity mezi typy operandů určenými podle doby vazby, dojde k chybě v době vyhodnocování vazby.

Poznámka: Toto pravidlo zabraňuje porovnáním, která nemohou považovat hodnoty odlišné odnull za rovné, protože jsou odkazy na instance různých typů delegátů. koncová poznámka

12.12.10 Operátory rovnosti mezi typy hodnot s možnou hodnotou null a literálem null

Operátory == a != umožňují, aby jeden operand byl hodnotou nulovatelného typu hodnoty a druhý operand byl literálem null, a to i v případě, že pro operaci neexistuje žádný předdefinovaný nebo uživatelem definovaný operátor (v nezvednuté nebo zvednuté formě).

Pro jednu z forem operace

x == null    null == x    x != null    null != x

Pokud je x výrazem typu hodnoty null a rozlišení přetížení operátoru (§12.4.5) nenajde použitelný operátor, výsledek se místo toho vypočítá z vlastnosti HasValue objektu x. Konkrétně první dvě formuláře jsou přeloženy do !x.HasValuea poslední dvě formuláře jsou přeloženy do x.HasValue.

12.12.11 Operátory rovnosti řazené kolekce členů

Operátory rovnosti u n-tic jsou aplikovány na prvky operandů n-tic ve dvojicích a v lexikálním pořadí.

Pokud je každý operand x a y operátoru == nebo != klasifikován buď jako n-tice, nebo jako hodnota s typem n-tice (§8.3.11), je operátor operátor rovnosti n-tic.

Pokud je operand e klasifikován jako řazená kolekce členů, jsou prvky, e1...en výsledkem vyhodnocení výrazů prvků výrazu řazené kolekce členů. Jinak je-li e hodnotou typu n-tice, musí být prvky t.Item1...t.Itemn, kde t je výsledkem vyhodnocení e.

Operandy x a y operátoru rovnosti n-tice musí mít stejný počet prvků, jinak dojde k chybě při kompilaci. Pro každou dvojici prvků xi a yise použije stejný operátor rovnosti, který poskytne výsledek typu bool, dynamic, typ, který má implicitní převod na bool, nebo typ, který definuje operátory true a false.

Operátor rovnosti n-tice x == y se vyhodnotí takto:

  • Operand na levé straně x je vyhodnocen.
  • Vyhodnocuje se pravý operand y.
  • Pro každou dvojici prvků xi a yi v lexikálním pořadí:
    • Operátor xi == yi se vyhodnotí a výsledek typu bool se získá následujícím způsobem:
      • Pokud porovnání přineslo bool, pak je to výsledek.
      • V opačném případě, pokud porovnání přineslo dynamic pak je operátor false dynamicky vyvolán a výsledná hodnota bool je negovaná s logickým operátorem negace (!).
      • Jinak platí, že pokud typ porovnání má implicitní převod na bool, použije se tento převod.
      • V opačném případě, pokud typ porovnání má operátor false, tento operátor je vyvolán a výsledná bool hodnota je negována s logickým operátorem negace (!).
    • Pokud je výsledná boolfalse, nedojde k žádnému dalšímu vyhodnocení a výsledek operátoru rovnosti n-tic je false.
  • Pokud všechna porovnání prvků přinesla true, výsledek operátoru rovnosti n-tice je true.

Operátor rovnosti n-tice x != y se vyhodnotí takto:

  • Vyhodnocuje se levý operand x.
  • Pravý operand y se vyhodnocuje.
  • Pro každou dvojici prvků xi a yi v lexikálním pořadí:
    • Operátor xi != yi se vyhodnotí a výsledek typu bool se získá následujícím způsobem:
      • Pokud porovnání přineslo výsledek bool, pak je to výsledek.
      • Jinak pokud porovnání přineslo dynamic, operátor true je na něm dynamicky vyvolán a výsledná bool hodnota je výsledkem.
      • Jinak platí, že pokud typ porovnání má implicitní převod na bool, použije se tento převod.
      • V opačném případě, pokud typ porovnání má operátor true, tento operátor je vyvolán a výsledná bool hodnota je výsledek.
    • Pokud je výsledná booltrue, nedojde k žádnému dalšímu vyhodnocení a výsledek operátoru rovnosti n-tice je true.
  • Pokud výsledkem všech porovnání prvků bylo false, výsledek operátoru rovnosti n-tice je false.

12.12.12 Operátor is

Existují dvě formy operátoru is. Jedním je operátor typu, který má typ na pravé straně. Druhý je operátor typu "is-pattern", který má vzor na pravé straně.

12.12.12.1 Operátor is-type

je operátor typu, který se používá ke kontrole, zda je typ objektu za běhu kompatibilní s daným typem. Kontrola se provádí za běhu. Výsledek operace E is T, kde E je výraz a T je typ jiný než dynamic, je logická hodnota označující, zda E je nenulová a lze ji úspěšně převést na typ T pomocí převodu podle odkazu, převodu boxingu, převodu unboxingu, převodu wrappingu nebo převodu unwrappingu.

Operace se vyhodnotí takto:

  1. Pokud E je anonymní funkce nebo skupina metod, dojde k chybě v době kompilace.
  2. Jestliže je E literálem null, nebo pokud je hodnota E rovna null, pak je výsledek false.
  3. Jinak:
  4. Nechte R být typem modulu runtime E.
  5. Pojďme D odvozovat z R následujícím způsobem:
  6. Pokud je R typem hodnoty, která může být null, pak je D základním typem R.
  7. V opačném případě D je R.
  8. Výsledek závisí na D a T následujícím způsobem:
  9. Pokud je T referenční typ, výsledek je true, pokud:
    • mezi D a Texistuje převod identity ,
    • D je typ odkazu a implicitní převod odkazu z D na T existuje nebo
    • Buď: D je typ hodnoty a existuje převod boxování z D na T.
      Nebo: D je typ hodnoty a T je typ rozhraní implementovaný D.
  10. Pokud je T nulovatelný typ hodnoty, výsledek je true, pokud je D základním typem T.
  11. Pokud je T hodnotový typ, který nepřipouští hodnotu null, výsledek je true, pokud jsou D a T stejného typu.
  12. V opačném případě je výsledek false.

Operátor is nebere v úvahu uživatelsky definované převody.

Poznámka: Při vyhodnocování operátoru is za běhu byly nahrazeny všechny argumenty typu a neexistují žádné otevřené typy (§8.4.3). koncová poznámka

Poznámka: Operátor is lze pochopit z hlediska typů kompilace a převodů následujícím způsobem, kde C je typ Ekompilace:

  • Je-li typ e kompilace stejný jako T, nebo pokud implicitní převod odkazu (§10.2.8), převod boxingu (§10.2.9T), převod obtékání (§10.6) nebo explicitní převod přebalování (§10.6) existuje z typu E kompilace na T:
    • Pokud je typ C hodnotový a nelze ho nullovat, výsledkem operace je true.
    • V opačném případě je výsledek operace ekvivalentní vyhodnocení E != null.
  • V opačném případě, pokud explicitní převod odkazu (§10.3.5) nebo převod rozbalení (§10.3.7) existuje od C do T, nebo je-li C nebo T otevřený typ (§8.4.3), budou provedeny kontroly za běhu, jak je popsáno výše.
  • Jinak není možné žádné odkazování, zabalení, rozbalení nebo zrušení převodu E na typ T a výsledek operace je false. Kompilátor může implementovat optimalizace na základě typu kompilace.

koncová poznámka

12.12.12.2 Operátor "is-pattern"

Operátor is-pattern slouží ke kontrole, jestli hodnota vypočítaná výrazem odpovídá danému vzoru (§11). Kontrola se provádí za běhu. Výsledek operátoru is-pattern je true, pokud hodnota odpovídá vzoru; jinak je false.

Pro výraz tvaru E is P, kde E je relační výraz typu T a P je vzorec, jedná se o chybu při kompilaci, pokud platí některá z následujících podmínek:

  • E neurčil hodnotu nebo nemá typ.
  • Vzor P nelze použít (§11.2) na typ T.

12.12.13 Operátor as

Operátor as se používá k explicitnímu převodu hodnoty na daný odkazový typ nebo nulovatelný typ hodnoty. Na rozdíl od výrazu pro přetypování (§12.9.7) operátor as nikdy nevyvolá výjimku. Pokud není uvedený převod možný, výsledná hodnota je null.

Při operaci ve formě E as Tmusí být E výrazem a T referenčním typem, parametrem typu známým jako referenční, nebo nulovatelným typem hodnoty. Kromě toho musí být splněna alespoň jedna z následujících podmínek, jinak dojde k chybě v době kompilace:

  • Identita (§10.2.2), implicitní nulovatelnost (§10.2.6), implicitní reference (§10.2.8), zabalení (§10.2.9), explicitní nulovatelnost (§10.3.4), explicitní reference (§10.3.5) nebo obtékání (§8.3.12) se provádí z E na T.
  • Typ E nebo T je otevřený typ.
  • E je doslovný výraz null.

Pokud typ za běhu kompilace E není dynamic, operace E as T vede ke stejnému výsledku jako

E is T ? (T)(E) : (T)null

kromě toho, že E se vyhodnotí pouze jednou. Kompilátor může být očekáváno, že optimalizuje E as T tak, aby prováděl maximálně jednu kontrolu typu modulu runtime, na rozdíl od dvou kontrol typu modulu runtime odvozených z výše uvedeného rozšíření.

Pokud je typ E kompilace dynamic, na rozdíl od operátoru přetypování není operátor as dynamicky vázán (§12.3.3). Proto rozšíření v tomto případě je:

E is T ? (T)(object)(E) : (T)null

Všimněte si, že některé převody, jako například uživatelsky definované převody, nejsou možné s operátorem as a místo toho by se měly provést pomocí výrazů přetypování.

Příklad: V příkladu

class X
{
    public string F(object o)
    {
        return o as string;  // OK, string is a reference type
    }

    public T G<T>(object o)
        where T : Attribute
    {
        return o as T;       // Ok, T has a class constraint
    }

    public U H<U>(object o)
    {
        return o as U;       // Error, U is unconstrained
    }
}

parametr typu TG je známý jako odkazový typ, protože má omezení třídy. Parametr typu U u H však nesplňuje požadované podmínky; proto je použití operátoru as v H zakázáno.

koncový příklad

12.13 Logické operátory

12.13.1 Obecné

Operátory &,^a | se nazývají logické operátory.

and_expression
    : equality_expression
    | and_expression '&' equality_expression
    ;

exclusive_or_expression
    : and_expression
    | exclusive_or_expression '^' and_expression
    ;

inclusive_or_expression
    : exclusive_or_expression
    | inclusive_or_expression '|' exclusive_or_expression
    ;

Pokud má operand logického operátoru typ kompilace dynamic, je výraz dynamicky vázán (§12.3.3). V tomto případě je typ kompilace výrazu dynamica rozlišení popsané níže proběhne za běhu pomocí typu běhu těchto operandů, které mají typ kompilačního času dynamic.

Pro operaci ve tvaru x «op» y, kde «op» je jedním z logických operátorů, je použito přetížení rozlišení (§12.4.5) pro výběr konkrétní implementace operátoru. Operandy jsou převedeny na typy parametrů vybraného operátoru a typ výsledku je návratový typ operátoru.

Předdefinované logické operátory jsou popsány v následujících dílčích kapitolách.

12.13.2 Celočíselné logické operátory

Předdefinované celočíselné logické operátory jsou:

int operator &(int x, int y);
uint operator &(uint x, uint y);
long operator &(long x, long y);
ulong operator &(ulong x, ulong y);

int operator |(int x, int y);
uint operator |(uint x, uint y);
long operator |(long x, long y);
ulong operator |(ulong x, ulong y);

int operator ^(int x, int y);
uint operator ^(uint x, uint y);
long operator ^(long x, long y);
ulong operator ^(ulong x, ulong y);

Operátor & vypočítá bitovou logickou hodnotu AND dvou operandů, operátor | vypočítá bitovou logickou hodnotu OR obou operandů a operátor ^ vypočítá bitovou logickou výhradní hodnotu OR obou operandů. Z těchto operací nejsou možné přetečení.

Zvednuté (§12.4.8) tvary předdefinovaných celočíselných logických operátorů definovaných výše jsou také předdefinovány.

12.13.3 – logické operátory výčtu

Každý typ výčtu E implicitně poskytuje následující předdefinované logické operátory:

E operator &(E x, E y);
E operator |(E x, E y);
E operator ^(E x, E y);

Výsledek vyhodnocení x «op» y, kde x a y jsou výrazy typu výčtu E s podkladovým typem U, a «op» je jeden z logických operátorů, je naprosto stejný jako vyhodnocení (E)((U)x «op» (U)y). Jinými slovy, logické operátory typu výčtu jednoduše provádějí logickou operaci u základního typu dvou operandů.

Zvednuté (§12.4.8) formy nezvednutých předdefinovaných logických operátorů výčtu definovaných výše jsou rovněž předdefinovány.

12.13.4 Booleovské logické operátory

Předdefinované Booleovské logické operátory jsou:

bool operator &(bool x, bool y);
bool operator |(bool x, bool y);
bool operator ^(bool x, bool y);

Výsledek x & y je true, pokud jsou jak x, tak y rovny true. V opačném případě je výsledek false.

Výsledek x | y je true, pokud je buď x, nebo y rovno true. V opačném případě je výsledek false.

Výsledek x ^ y je true, pokud je xtrue a y je falsenebo x je false a y je true. V opačném případě je výsledek false. Pokud jsou operandy typu bool, operátor ^ vypočítá stejný výsledek jako operátor !=.

12.13.5 Boolean s možnou hodnotou null & a | operátoři

Booleanový typ s možnou hodnotou null bool? může představovat tři hodnoty, true, falsea null.

Stejně jako u ostatních binárních operátorů jsou také předem definovány zvednuté tvary logických operátorů & a | (§12.13.4).

bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);

Sémantika zdvižených operátorů & a | jsou definována v následující tabulce:

x y x & y x \| y
true true true true
true false false true
true null null true
false true false true
false false false false
false null false null
null true null true
null false false null
null null null null

Poznámka: Typ bool? se koncepčně podobá typu se třemi hodnotami používaným pro logické výrazy v SQL. Výše uvedená tabulka se řídí stejnou sémantikou jako SQL, zatímco použití pravidel §12.4.8 na operátory & a | by tomu neodpovídalo. Pravidla §12.4.8 již poskytují SQL podobnou sémantiku pro rozšířený operátor ^. koncová poznámka

12.14 Podmíněné logické operátory

12.14.1 Obecné

Operátory && a || se nazývají podmíněné logické operátory. označují se také jako „logické operátory s krátkým obvodem“.

conditional_and_expression
    : inclusive_or_expression
    | conditional_and_expression '&&' inclusive_or_expression
    ;

conditional_or_expression
    : conditional_and_expression
    | conditional_or_expression '||' conditional_and_expression
    ;

Operátory && a || jsou podmíněné verze operátorů & a |:

  • Operace x && y odpovídá operaci x & ys tím rozdílem, že y je vyhodnocena pouze v případě, že x není false.
  • Operace x || y odpovídá operaci x | ys tím rozdílem, že y je vyhodnocena pouze v případě, že x není true.

Poznámka: Důvodem, proč zkratování používá podmínky „pravdivý“ a „nepravdivý“, je umožnit uživatelsky definovaným podmíněným operátorům určit, kdy se má zkratování aplikovat. Uživatelem definované typy můžou být ve stavu, kdy operator true vrací false a operator false vrací false. V takových případech by && ani || nezkratovaly. koncová poznámka

Pokud má operand podmíněného logického operátoru typ kompilace dynamic, je výraz dynamicky vázán (§12.3.3). V tomto případě je typ kompilace výrazu dynamica rozlišení popsané níže proběhne za běhu pomocí typu běhu těchto operandů, které mají typ kompilačního času dynamic.

Operace formuláře x && y nebo x || y je zpracována použitím rozlišení přetížení (§12.4.5), jako by byla operace zapsána x & y nebo x | y. Potom

  • Pokud rozlišení přetížení nenajde jediný nejlepší operátor nebo pokud rozlišení přetížení vybere jednoho z předdefinovaných logických operátorů pro celá čísla nebo logické operátory pro typ Nullable Boolean (§12.13.5), výsledkem je chyba vazby.
  • V opačném případě, pokud je vybraný operátor jedním z předdefinovaných Booleových logických operátorů (§12.13.4), je operace zpracována dle popisu v §12.14.2.
  • V opačném případě je vybraným operátorem operátor definovaný uživatelem a operace je zpracována, jak je popsáno v §12.14.3.

Podmíněné logické operátory není možné přímo přetěžovat. Vzhledem k tomu, že se podmíněné logické operátory vyhodnocují z hlediska běžných logických operátorů, přetížení běžných logických operátorů jsou s určitými omezeními také považována za přetížení podmíněných logických operátorů. Toto je popsáno dále v §12.14.3.

12.14.2 Booleovské podmínkové logické operátory

Pokud jsou operandy && nebo || typu boolnebo pokud jsou operandy typu, které nedefinují použitelné operator & nebo operator |, ale definují implicitní převody na bool, operace se zpracuje takto:

  • Operace x && y se vyhodnotí jako x ? y : false. Jinými slovy, x je nejprve vyhodnocen a převeden na typ bool. Pokud je xtrue, y se vyhodnotí a převede na typ boola stane se výsledkem operace. V opačném případě je výsledek operace false.
  • Operace x || y se vyhodnotí jako x ? true : y. Jinými slovy, x je nejprve vyhodnocen a převeden na typ bool. Pokud je xtrue, výsledek operace je true. V opačném případě se y vyhodnotí a převede na typ boola stane se výsledkem operace.

12.14.3 Uživatelem definované podmíněné logické operátory

Jsou-li operandy && nebo || typy, které deklarují použitelné uživatelem definované operator & nebo operator |, platí obě následující podmínky, pokud T je typ, ve kterém je vybraný operátor deklarován:

  • Návratový typ a typ každého parametru vybraného operátoru musí být T. Jinými slovy, operátor vypočítá logický operátor AND nebo logický operátor OR dvou operandů typu Ta vrátí výsledek typu T.
  • T má obsahovat prohlášení operator true a operator false.

K chybě času vazby dochází v případě, že jakýkoli z těchto požadavků není splněn. Jinak se operace && nebo || vyhodnotí kombinací uživatelem definovaného operator true nebo operator false s vybraným uživatelem definovaným operátorem:

  • Operace x && y je vyhodnocena jako T.false(x) ? x : T.&(x, y), kde T.false(x) je vyvolání operator false deklarovaného v Ta T.&(x, y) je vyvoláním vybraného operator &. Jinými slovy, x se nejprve vyhodnotí a operator false se vyvolá na výsledku, aby se zjistilo, jestli je x rozhodně nepravdivý. Pokud x je rozhodně nepravda, výsledek operace je hodnota, která byla dříve vypočtena pro x. V opačném případě se y vyhodnotí a vybraná operator & se vyvolá na hodnotě dříve vypočítané pro x a hodnotě vypočítané pro y pro získání výsledku operace.
  • Operace x || y je vyhodnocena jako T.true(x) ? x : T.|(x, y), kde T.true(x) je vyvolání operator true deklarovaného v Ta T.|(x, y) je vyvoláním vybraného operator |. Jinými slovy, x se nejprve vyhodnotí a operator true se vyvolá na výsledek, který určí, jestli x je rozhodně pravdivá. Pokud je x rozhodně pravdivý, výsledek operace je hodnota, která byla dříve vypočtena pro x. V opačném případě se vyhodnotí y a vybraná operator | se aplikuje na hodnotu dříve vypočítanou pro x a na hodnotu vypočítanou pro y, aby se vytvořil výsledek operace.

V obou těchto operacích se výraz zadaný x vyhodnotí pouze jednou a výraz zadaný y se buď nevyhodnotí, nebo se vyhodnotí přesně jednou.

12.15 Operátor sjednocení null

Operátor ?? se nazývá operátor nulového sjednocení.

null_coalescing_expression
    : conditional_or_expression
    | conditional_or_expression '??' null_coalescing_expression
    | throw_expression
    ;

Ve výrazu nulového slučování a ?? b, jestliže a nenínull, výsledek je a; jinak je výsledek b. Operace vyhodnotí b pouze v případě, že je anull.

Operátor nulového sjednocení je asociativní zprava, což znamená, že operace jsou seskupeny zprava doleva.

Příklad: Výraz ve tvaru a ?? b ?? c se vyhodnotí jako ?? (b ?? c). Obecně řečeno, výraz formuláře E1 ?? E2 ?? ... ?? EN vrátí první z operandů, které nejsounull, nebo null, pokud jsou všechny operandy null. konec příkladu

Typ výrazu a ?? b závisí na tom, které implicitní převody jsou k dispozici na operandech. V pořadí podle priority je typ a ?? bA₀, Anebo B, kde A je typem a (za předpokladu, že a má typ), B je typem b(za předpokladu, že b má typ) a A₀ je podkladovým typem A, pokud je A typ nullovatelné hodnoty, nebo A jinak. Konkrétně se a ?? b zpracuje takto:

  • Pokud A existuje a není typ hodnoty null nebo odkazový typ, dojde k chybě v době kompilace.
  • V opačném případě pokud A existuje a b je dynamický výraz, typ výsledku je dynamic. V době běhu se nejprve vyhodnotí a. Pokud a není null, a se převede na dynamica stane se výsledkem. V opačném případě se b vyhodnotí a stane se výsledkem.
  • Jinak pokud A existuje a je typ hodnoty null a implicitní převod existuje z b na A₀, je typ výsledku A₀. Během běhu programu se nejprve vyhodnotí a. Pokud a není null, a se rozbalí na typ A₀a to se stane výsledkem. V opačném případě se b vyhodnotí a převede na typ A₀a stane se výsledkem.
  • V opačném případě, pokud A existuje a implicitní převod existuje z b na A, je typ výsledku A. Za běhu se nejprve vyhodnotí a. Pokud a není null, stane se výsledkem a. V opačném případě se b vyhodnotí a převede na typ Aa stane se výsledkem.
  • V opačném případě, pokud A existuje a je nulovatelný typ hodnoty, b má typ B a existuje implicitní převod z A₀ na B, pak je typ výsledku B. V době běhu se nejprve vyhodnotí a. Pokud a není null, a je rozbalený na typ A₀, převeden na typ Ba stane se výsledkem. V opačném případě se b vyhodnotí a stane se výsledkem.
  • V opačném případě, pokud b má typ B a implicitní převod existuje z a na B, je typ výsledku B. V době běhu se nejprve vyhodnotí a. Pokud a není null, a se převede na typ Ba stane se výsledkem. V opačném případě se b vyhodnotí a stane se výsledkem.

V opačném případě jsou a a b nekompatibilní a a dojde k chybě v době kompilace.

12.16 Operátor výrazu throw

throw_expression
    : 'throw' null_coalescing_expression
    ;

throw_expression vyhodí hodnotu vytvořenou vyhodnocením null_coalescing_expression. Výraz se implicitně převede na System.Exceptiona výsledek vyhodnocení výrazu se před vyvoláním převede na System.Exception. Chování při vyhodnocování výrazu throw je stejné jako u příkazu throw (§13.10.6).

throw_expression nemá žádný typ. throw_expression je převoditelný na každý typ implicitní vyvolá převod.

Výraz throw nastane pouze v následujících syntaktických kontextech:

  • Jako druhý nebo třetí operand ternárního podmíněného operátoru (?:).
  • Jako druhý operand operátoru nulového sjednocení (??).
  • Jako tělo lambda výrazu nebo členu.

12.17 Výrazy deklarace

Výraz deklarace deklaruje místní proměnnou.

declaration_expression
    : local_variable_type identifier
    ;

local_variable_type
    : type
    | 'var'
    ;

simple_name_ se také považuje za výraz deklarace, pokud vyhledávání jednoduchých názvů nenašlo přidruženou deklaraci (§12.8.4). Při použití jako výraz deklarace se _ nazývá jednoduché zahození. Je séanticky ekvivalentní var _, ale je povolen na více místech.

Výraz deklarace se provádí pouze v následujících syntaktických kontextech:

  • Jako outargument_value v argument_list.
  • Jako prosté zahození _, které tvoří levou stranu prostého přiřazení (§12.21.2).
  • Jako tuple_element v jednom nebo více rekurzivně vnořených tuple_expressions, které se skládají z levé strany dekonstrukčního přiřazení. deconstruction_expression vyvolává výrazy deklarace v této pozici, i když výrazy deklarace nejsou syntakticky přítomny.

Poznámka: To znamená, že deklarativní výraz nelze dávat do závorek. koncová poznámka

Jedná se o chybu pro implicitně typovanou proměnnou deklarovanou s declaration_expression, na které se má odkazovat v argument_list, kde je deklarována.

Je chybou, pokud je proměnná deklarovaná s declaration_expression odkazována v rámci dekonstrukčního přiřazení, ve kterém se nachází.

Výraz deklarace, který představuje jednoduché zahození, nebo kde local_variable_type je identifikátor var, je klasifikován jako implicitně typovaná proměnná. Výraz nemá žádný typ a typ místní proměnné je odvozen na základě syntaktického kontextu následujícím způsobem:

  • V argument_list odvozený typ proměnné je deklarovaný typ odpovídajícího parametru.
  • Jako levá strana jednoduchého přiřazení je odvozený typ proměnné stejný jako typ pravé strany přiřazovacího výrazu.
  • V n-tice na levé straně jednoduchého přiřazení je odvozený typ proměnné typem odpovídajícího prvku n-tice na pravé straně přiřazení (po dekonstrukci).

V opačném případě se výraz deklarace klasifikuje jako explicitně typovaná proměnná a typ výrazu i deklarované proměnné musí být ten, který je určen local_variable_type.

Výraz deklarace s identifikátorem _ je zahození (§9.2.9.2) a nezavádí název proměnné. Výraz deklarace s identifikátorem jiným než _ zavádí dané jméno do nejbližšího obklopujícího prostoru deklarace místní proměnné (§7.3).

Příklad :

string M(out int i, string s, out bool b) { ... }

var s1 = M(out int i1, "One", out var b1);
Console.WriteLine($"{i1}, {b1}, {s1}");
// Error: i2 referenced within declaring argument list
var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2);
var s3 = M(out int _, "Three", out var _);

Deklarace s1 ukazuje explicitně i implicitně zadané výrazy deklarace. Odvozený typ b1 je bool, protože je to typ odpovídajícího výstupního parametru v M1. Následující WriteLine má přístup k i1 a b1, které byly zavedeny v uzavřeném oboru.

Deklarace s2 ukazuje pokus o použití i2 ve vnořeném volání M, což je zakázáno, protože odkaz se vyskytuje v seznamu argumentů, kde i2 byl deklarován. Na druhou stranu je povolen odkaz na b2 v posledním argumentu, protože k němu dochází po konci vnořeného seznamu argumentů, kde b2 byl deklarován.

Deklarace s3 ukazuje použití implicitně i explicitně zadaných deklarativních výrazů, které se ignorují. Vzhledem k tomu, že zahození neeklarují pojmenovanou proměnnou, je povoleno více výskytů identifikátoru _.

(int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5);

Tento příklad ukazuje použití implicitně a explicitně typovaných deklarací výrazů pro proměnné i zahození v rámci dekonstruujícího přiřazení. simple_name_ je ekvivalentní var _, pokud se nenajde žádná deklarace _.

void M1(out int i) { ... }

void M2(string _)
{
    M1(out _);      // Error: `_` is a string
    M1(out var _);
}

Tento příklad ukazuje použití var _ k poskytnutí implicitně zadaného zahození, pokud _ není k dispozici, protože označuje proměnnou v nadřazeném oboru.

konec příkladu

12.18 Podmíněný operátor

Operátor ?: se nazývá podmíněný operátor. Někdy se také nazývá ternární operátor.

conditional_expression
    : null_coalescing_expression
    | null_coalescing_expression '?' expression ':' expression
    | null_coalescing_expression '?' 'ref' variable_reference ':'
      'ref' variable_reference
    ;

Výraz throw (§12.16) není povolen v podmíněném operátoru, pokud je ref.

Podmíněný výraz formuláře b ? x : y nejprve vyhodnotí podmínku b. Pokud je btrue, x se vyhodnotí a stane se výsledkem operace. V opačném případě se y vyhodnotí a stane se výsledkem operace. Podmíněný výraz nikdy nevyhodnocuje x i y.

Podmíněný operátor má pravou asociativitu, což znamená, že operace jsou seskupené zprava doleva.

Příklad: Výraz tvaru a ? b : c ? d : e se vyhodnotí jako a ? b : (c ? d : e). konec příkladu

Prvním operandem operátoru ?: musí být výraz, který lze implicitně převést na boolnebo výraz typu, který implementuje operator true. Pokud není splněna žádná z těchto požadavků, dojde k chybě v době kompilace.

Pokud je k dispozici ref:

  • Převod identity musí existovat mezi typy dvou variable_references a typem výsledku může být jeden typ. Pokud je některý typ dynamic, odvození typu dává přednost dynamic (§8.7). Pokud je některý z typů typu n-tice (§8.3.11), zahrnuje typová inferenci názvy prvků, pokud se názvy prvků ve stejné pořadové pozici shodují v obou n-ticích.
  • Výsledkem je odkaz na proměnnou, která je zapisovatelná, pokud je možné zapisovat oba variable_references.

Poznámka: Pokud ref existuje, vrátí conditional_expression odkaz na proměnnou, která se dá přiřadit k referenční proměnné pomocí operátoru = ref nebo předán jako parametr reference/input/output. koncová poznámka

Pokud ref není k dispozici, druhý a třetí operand, x a y, operátoru ?: řídí typ podmíněného výrazu:

  • Pokud má x typ X a y typ Y,
    • Pokud mezi X a Yexistuje převod identity, je výsledkem nejlepší společný typ sady výrazů (§12.6.3.15). Pokud je některý typ dynamic, odvození typu dává přednost dynamic (§8.7). Pokud je některý z typů typem n-tice (§8.3.11), odvození typu zahrnuje názvy prvků, pokud se názvy prvků ve stejné pořadové pozici shodují v obou n-ticích.
    • Jinak, pokud implicitní převod (§10.2) existuje z X na Y, ale ne z Y na X, pak Y je typ podmíněného výrazu.
    • Jinak, pokud existuje implicitní převod výčtu (§10.2.4) z X na Y, pak je Y typem podmíněného výrazu.
    • Pokud jinak existuje implicitní převod výčtu (§10.2.4) z Y na X, pak typem podmíněného výrazu je X.
    • Jinak, pokud implicitní převod (§10.2) existuje z Y na X, ale ne z X na Y, pak X je typ podmíněného výrazu.
    • V opačném případě nelze určit žádný typ výrazu a dojde k chybě v době kompilace.
  • Pokud má typ jenom jeden z x a y a x i y jsou implicitně konvertibilní na tento typ, pak se jedná o typ podmíněného výrazu.
  • V opačném případě nelze určit žádný typ výrazu a dojde k chybě v době kompilace.

Zpracování podmíněného výrazu ref formuláře b ? ref x : ref y se skládá z následujících kroků:

  • Nejprve se vyhodnotí b a určí se hodnota boolb:
    • Pokud existuje implicitní převod z typu b na bool, tento implicitní převod se provede a vytvoří hodnotu bool.
    • V opačném případě je vyvolána operator true definovaná typem b, aby se vytvořila hodnota bool.
  • Pokud bool hodnota vytvořená výše uvedeným krokem je true, x se vyhodnotí a výsledný odkaz na proměnnou se stane výsledkem podmíněného výrazu.
  • V opačném případě se vyhodnotí y a výsledný odkaz na proměnnou se stane výsledkem podmíněného výrazu.

Zpracování podmíněného výrazu ve tvaru b ? x : y za běhu se skládá z následujících kroků:

  • Nejprve se vyhodnotí b a určí se hodnota boolb:
    • Pokud existuje implicitní převod z typu b na bool, provede se tento implicitní převod a vytvoří hodnotu bool.
    • V opačném případě je vyvolána operator true definovaná typem b, aby se vytvořila hodnota bool.
  • Pokud bool hodnota vytvořená výše uvedeným krokem je true, x se vyhodnotí a převede na typ podmíněného výrazu a stane se výsledkem podmíněného výrazu.
  • V opačném případě se y vyhodnotí a převede na typ podmíněného výrazu a stane se výsledkem podmíněného výrazu.

12.19 Anonymní funkční výrazy

12.19.1 Obecné

anonymní funkce je výraz, který představuje definici metody in-line. Anonymní funkce sama o sobě nemá hodnotu ani typ, ale je převoditelná na kompatibilní typ delegátu nebo stromu výrazů. Vyhodnocení převodu anonymní funkce závisí na cílovém typu převodu: Pokud se jedná o typ delegáta, převod se vyhodnotí na hodnotu delegáta odkazující na metodu, kterou anonymní funkce definuje. Pokud se jedná o typ stromu výrazů, převod se vyhodnotí na strom výrazu, který představuje strukturu metody jako strukturu objektu.

Poznámka: Z historických důvodů existují dvě syntaktické varianty anonymních funkcí, konkrétně lambda_expressions a anonymous_method_expressions. Pro téměř všechny účely jsou lambda_expressions stručnější a výstižnější než anonymous_method_expressions, které zůstávají v jazyce pro zpětnou kompatibilitu. koncová poznámka

lambda_expression
    : 'async'? anonymous_function_signature '=>' anonymous_function_body
    ;

anonymous_method_expression
    : 'async'? 'delegate' explicit_anonymous_function_signature? block
    ;

anonymous_function_signature
    : explicit_anonymous_function_signature
    | implicit_anonymous_function_signature
    ;

explicit_anonymous_function_signature
    : '(' explicit_anonymous_function_parameter_list? ')'
    ;

explicit_anonymous_function_parameter_list
    : explicit_anonymous_function_parameter
      (',' explicit_anonymous_function_parameter)*
    ;

explicit_anonymous_function_parameter
    : anonymous_function_parameter_modifier? type identifier
    ;

anonymous_function_parameter_modifier
    : 'ref'
    | 'out'
    | 'in'
    ;

implicit_anonymous_function_signature
    : '(' implicit_anonymous_function_parameter_list? ')'
    | implicit_anonymous_function_parameter
    ;

implicit_anonymous_function_parameter_list
    : implicit_anonymous_function_parameter
      (',' implicit_anonymous_function_parameter)*
    ;

implicit_anonymous_function_parameter
    : identifier
    ;

anonymous_function_body
    : null_conditional_invocation_expression
    | expression
    | 'ref' variable_reference
    | block
    ;

Při rozpoznání anonymous_function_body, pokud jsou použitelné jak null_conditional_invocation_expression, tak výraz jako alternativy, zvolí se ta první.

Note: Překrývání a prioritizace mezi alternativami je zde určeno pouze pro popisné pohodlí; gramatická pravidla by mohla být propracovaná k odstranění tohoto překrývání. ANTLR a další gramatické systémy přijímají stejné pohodlí, takže anonymous_function_body má zadanou sémantiku automaticky. koncová poznámka

Poznámka: Pokud se považuje za výraz , syntaktická forma, například x?.M(), by byla chyba, pokud je typ výsledku Mvoid (§12.8.13). Pokud se však považuje za null_conditional_invocation_expression, je povolen typ výsledku void. koncová poznámka

Příklad: Typ výsledku List<T>.Reverse je void. V následujícím kódu je tělo anonymního výrazu null_conditional_invocation_expression, takže se nejedná o chybu.

Action<List<int>> a = x => x?.Reverse();

koncový příklad

Operátor => má stejnou prioritu jako přiřazení (=) a je pravostranně asociativní.

Anonymní funkce s modifikátorem async je asynchronní funkce a dodržuje pravidla popsaná v §15.15.

Parametry anonymní funkce ve formě lambda_expression mohou být explicitně nebo implicitně zadány. V seznamu explicitně zadaných parametrů je explicitně uveden typ každého parametru. V seznamu implicitně zadaných parametrů jsou typy parametrů odvozeny z kontextu, ve kterém se vyskytuje anonymní funkce – konkrétně, když je anonymní funkce převedena na kompatibilní typ delegáta nebo typ stromu výrazů, tento typ poskytuje typy parametrů (§10,7).

V lambda_expression s jedním implicitně zadaným parametrem mohou být závorky ze seznamu parametrů vynechány. Jinými slovy, anonymní funkce formuláře

( «param» ) => «expr»

lze zkrátit na

«param» => «expr»

Seznam parametrů anonymní funkce ve formě anonymous_method_expression je volitelný. Jsou-li zadány, parametry musí být explicitně zadány. Pokud ne, anonymní funkce je převeditelná na delegáta, který může mít libovolný seznam parametrů neobsahujících výstupní parametry.

Blok těla anonymní funkce je vždy dosažitelný (§13.2).

Příklad: Některé příklady anonymních funkcí následují níže:

x => x + 1                             // Implicitly typed, expression body
x => { return x + 1; }                 // Implicitly typed, block body
(int x) => x + 1                       // Explicitly typed, expression body
(int x) => { return x + 1; }           // Explicitly typed, block body
(x, y) => x * y                        // Multiple parameters
() => Console.WriteLine()              // No parameters
async (t1,t2) => await t1 + await t2   // Async
delegate (int x) { return x + 1; }     // Anonymous method expression
delegate { return 1 + 1; }             // Parameter list omitted

konec příkladu

Chování lambda_expressions a anonymous_method_expressions je stejné s výjimkou následujících bodů:

  • anonymous_method_expressionumožňuje, aby byl seznam parametrů zcela vynechán, což umožňuje převoditelnost na delegátové typy libovolného seznamu parametrů hodnot.
  • lambda_expressionumožňují vynechání typů parametrů a jejich odvození, zatímco anonymous_method_expressionvyžadují explicitní uvedení typů parametrů.
  • Tělo lambda_expression může být výraz nebo blok, zatímco tělo anonymous_method_expression musí být blokem.
  • Pouze lambda_expressionmají převody na kompatibilní typy stromu výrazů (§8.6).

12.19.2 Anonymní podpisy funkcí

signatura anonymní funkce definuje názvy a volitelně typy parametrů pro anonymní funkci. Rozsah parametrů anonymní funkce je anonymous_function_body (§7.7). Spolu se seznamem parametrů (pokud je zadán) anonymní tělo metody představuje prostor deklarace (§7.3). Jedná se tedy o chybu v době kompilace pro název parametru anonymní funkce tak, aby odpovídal názvu místní proměnné, místní konstanty nebo parametru, jehož obor zahrnuje anonymous_method_expression nebo lambda_expression.

Pokud má anonymní funkce explicit_anonymous_function_signature, je sada kompatibilních typů delegátů a typů stromu výrazů omezena na ty, které mají stejné typy parametrů a modifikátory ve stejném pořadí (§10.7). Na rozdíl od převodů skupin metod (§10,8), není podporována kontra variance anonymních typů parametrů funkce. Pokud anonymní funkce nemá anonymous_function_signature, je sada kompatibilních typů delegátů a typů stromu výrazů omezena na ty, které nemají žádné výstupní parametry.

Všimněte si, že anonymous_function_signature nemůže obsahovat atributy ani pole parametrů. Nicméně anonymous_function_signature může být kompatibilní s typem delegáta, jehož seznam parametrů obsahuje parametrické pole.

Všimněte si také, že převod na typ stromu výrazů, i když je kompatibilní, může v době kompilace selhat (§8.6).

12.19.3 Těla anonymních funkcí

Tělo anonymní funkce (výraz nebo blok) podléhá následujícím pravidlům:

  • Pokud anonymní funkce obsahuje podpis, jsou parametry zadané v podpisu k dispozici v textu. Pokud anonymní funkce nemá podpis, lze ji převést na typ delegáta nebo typ výrazu s parametry (§10.7), ale k parametrům nelze v těle funkce přistupovat.
  • S výjimkou parametrů podle reference zadaných v podpisu (pokud nějaké jsou) nejbližší uzavírající anonymní funkce se jedná o chybu při kompilaci, pokud tělo přistupuje k parametru podle reference.
  • Kromě parametrů uvedených v podpisu (pokud nějaké existují) nejbližší obalující anonymní funkce je chyba při kompilaci, pokud se tělo snaží přistoupit k parametru typu ref struct.
  • Pokud je typ this typu struktury, jedná se o chybu kompilace, když se tělo pokusí přistoupit k this. To platí, zda je přístup explicitní (jako v this.x) nebo implicitní (stejně jako v x, kde x je členem instance struktury). Toto pravidlo jednoduše zakáže takový přístup a nemá vliv na to, zda vyhledávání členů vede k členu struktury.
  • Tělo má přístup k vnějším proměnným (§12.19.6) anonymní funkce. Přístup vnější proměnné bude odkazovat na instanci proměnné, která je aktivní v době vyhodnocení lambda_expression nebo anonymous_method_expression (§12.19.7).
  • Jedná se o chybu v době kompilace, pokud tělo obsahuje příkaz goto, příkaz break nebo příkaz continue, jehož cíl je mimo tělo nebo uvnitř těla anonymní funkce.
  • Příkaz return v těle vrátí ovládací prvek z vyvolání nejbližší anonymní funkce, ne z nadřazeného členu funkce.

Není výslovně stanoveno, zda existuje nějaký způsob, jak spustit blok anonymní funkce jinak než prostřednictvím vyhodnocení a vyvolání lambda_expression nebo anonymous_method_expression. Konkrétně se kompilátor může rozhodnout implementovat anonymní funkci tím, že syntetizuje jednu nebo více pojmenovaných metod nebo typů. Názvy všech těchto syntetizovaných prvků mají formu vyhrazenou pro použití kompilátoru (§6.4.3).

12.19.4 Rozlišení přetížení

Anonymní funkce v seznamu argumentů se účastní odvozování typů a rozlišení přetížení. Konkrétní pravidla najdete v §12.6.3 a §12.6.4.

Příklad: Následující příklad ukazuje vliv anonymních funkcí na rozlišení přetížení.

class ItemList<T> : List<T>
{
    public int Sum(Func<T, int> selector)
    {
        int sum = 0;
        foreach (T item in this)
        {
            sum += selector(item);
        }
        return sum;
    }

    public double Sum(Func<T, double> selector)
    {
        double sum = 0;
        foreach (T item in this)
        {
            sum += selector(item);
        }
        return sum;
    }
}

Třída ItemList<T> má dvě Sum metody. Každá z nich přebírá selector argument, který extrahuje hodnotu, která se má sečíst z položky seznamu. Extrahovaná hodnota může být buď int, nebo double a výsledný součet je podobně int nebo double.

Metody Sum lze například použít k výpočtu součtů ze seznamu řádků podrobností v pořadí.

class Detail
{
    public int UnitCount;
    public double UnitPrice;
    ...
}

class A
{
    void ComputeSums()
    {
        ItemList<Detail> orderDetails = GetOrderDetails( ... );
        int totalUnits = orderDetails.Sum(d => d.UnitCount);
        double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
        ...
    }

    ItemList<Detail> GetOrderDetails( ... )
    {
        ...
    }
}

Při prvním vyvolání orderDetails.Sumplatí obě metody Sum, protože anonymní d => d.UnitCount funkce je kompatibilní s Func<Detail,int> i s Func<Detail,double>. Řešení přetížení však vybere první Sum metodu, protože převod na Func<Detail,int> je lepší než převod na Func<Detail,double>.

Při druhém vyvolání orderDetails.Sumlze použít pouze druhou metodu Sum, protože anonymní funkce d => d.UnitPrice * d.UnitCount vytvoří hodnotu typu double. Rozlišení přetížení proto vybere druhou Sum metodu pro toto vyvolání.

konec příkladu

12.19.5 Anonymní funkce a dynamické vazby

Anonymní funkce nemůže být přijímačem, argumentem nebo operandem dynamicky vázané operace.

12.19.6 Vnější proměnné

12.19.6.1 Obecné

Jakákoli místní proměnná, parametr hodnoty nebo pole parametrů, jejichž obor zahrnuje lambda_expression nebo anonymous_method_expression, se nazývá vnější proměnná anonymní funkce. V instanci funkce člen třídy je tato hodnota považována za parametr hodnoty a je vnější proměnnou jakékoli anonymní funkce obsažené v členu funkce.

12.19.6.2 Zachycené vnější proměnné

Když na vnější proměnnou odkazuje anonymní funkce, říká se, že vnější proměnná byla zachycena anonymní funkcí. Obvykle je životnost místní proměnné omezena na provádění bloku nebo příkazu, ke kterému je přidružen (§9.2.9.1). Životnost zachycené vnější proměnné je však rozšířena alespoň dokud se delegát nebo strom výrazu vytvořený z anonymní funkce nestane způsobilým pro garbage collection.

Příklad: V příkladu

delegate int D();

class Test
{
    static D F()
    {
        int x = 0;
        D result = () => ++x;
        return result;
    }

    static void Main()
    {
        D d = F();
        Console.WriteLine(d());
        Console.WriteLine(d());
        Console.WriteLine(d());
    }
}

místní proměnná x je zachycena anonymní funkcí a doba životnosti x je rozšířena alespoň tak dlouho, dokud delegát vrácený z F nebude způsobilý pro uvolňování paměti. Vzhledem k tomu, že každé vyvolání anonymní funkce funguje na stejné instanci x, výstupem tohoto příkladu je:

1
2
3

konec příkladu

Pokud je místní proměnná nebo parametr hodnoty zachycen anonymní funkcí, místní proměnná nebo parametr se už nepovažuje za pevnou proměnnou (§23,4), ale považuje se za pohyblivou proměnnou. Zachycené vnější proměnné však nelze použít v příkazu fixed (§23.7), takže adresu zachycené vnější proměnné nelze vzít.

Poznámka: Na rozdíl od nezachycené proměnné může být zachycená místní proměnná současně vystavena více vláknům provádění. koncová poznámka

12.19.6.3 Vytvoření instance místních proměnných

Místní proměnná se považuje za inicializovanou, když se provádění dostane do oboru platnosti proměnné.

Příklad: Například při vyvolání následující metody se místní proměnná x vytvoří instance a inicializuje třikrát – jednou pro každou iteraci smyčky.

static void F()
{
    for (int i = 0; i < 3; i++)
    {
        int x = i * 2 + 1;
        ...
    }
}

Přesunutím deklarace x mimo smyčku však vznikne jedna instance x:

static void F()
{
    int x;
    for (int i = 0; i < 3; i++)
    {
        x = i * 2 + 1;
        ...
    }
}

ukončit příklad

Pokud není zachyceno, neexistuje způsob, jak přesně sledovat, jak často se vytváří instance místní proměnné, a protože životnosti instancí jsou oddělené, je možné, aby každá instance jednoduše používala stejné paměťové místo. Když ale anonymní funkce zachytí místní proměnnou, důsledky vytváření instance se stanou zjevné.

Příklad: Příklad

delegate void D();
class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        for (int i = 0; i < 3; i++)
        {
            int x = i * 2 + 1;
            result[i] = () => Console.WriteLine(x);
        }
        return result;
    }

    static void Main()
    {
        foreach (D d in F())
        {
            d();
        }
    }
}

vytvoří výstup:

1
3
5

Pokud se však deklarace x přesune mimo smyčku:

delegate void D();

class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        int x;
        for (int i = 0; i < 3; i++)
        {
            x = i * 2 + 1;
            result[i] = () => Console.WriteLine(x);
        }
        return result;
   }

   static void Main()
   {
       foreach (D d in F())
       {
           d();
       }
   }
}

výstup je:

5
5
5

Všimněte si, že kompilátor má povoleno (ale není povinen) optimalizovat tři instancí do jednoho objektu delegáta (§10.7.2).

konec příkladu

Pokud smyčka for-loop deklaruje proměnnou iterace, je tato proměnná považována za deklarovanou mimo smyčku.

příklad: Pokud se tedy příklad změní tak, aby zachytil samotnou iterační proměnnou:

delegate void D();

class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        for (int i = 0; i < 3; i++)
        {
            result[i] = () => Console.WriteLine(i);
        }
        return result;
   }

   static void Main()
   {
       foreach (D d in F())
       {
           d();
       }
   }
}

Zachytí se pouze jedna instance proměnné iterace, která vytvoří výstup:

3
3
3

konec příkladu

Anonymní delegáti funkce mohou sdílet některé zachycené proměnné, ale mají samostatné instance jiných.

Příklad: Pokud se například F změní na

static D[] F()
{
    D[] result = new D[3];
    int x = 0;
    for (int i = 0; i < 3; i++)
    {
        int y = 0;
        result[i] = () => Console.WriteLine($"{++x} {++y}");
    }
    return result;
}

tři delegáti zachycují stejnou instanci x ale samostatné instance ya výstup je:

1 1
2 1
3 1

konec příkladu

Samostatné anonymní funkce mohou zachytit stejnou instanci vnější proměnné.

příklad: V příkladu:

delegate void Setter(int value);
delegate int Getter();

class Test
{
    static void Main()
    {
        int x = 0;
        Setter s = (int value) => x = value;
        Getter g = () => x;
        s(5);
        Console.WriteLine(g());
        s(10);
        Console.WriteLine(g());
    }
}

tyto dvě anonymní funkce zachytávají stejnou instanci místní proměnné xa mohou tak "komunikovat" prostřednictvím této proměnné. Výstupem příkladu je:

5
10

konec příkladu

12.19.7 Vyhodnocení anonymních výrazů funkce

Anonymní funkce F musí být vždy převedena na typ delegáta D nebo typ stromové struktury výrazu E, a to buď přímo, nebo prostřednictvím výrazu vytvoření delegáta new D(F). Tento převod určuje výsledek anonymní funkce, jak je popsáno v §10.7.

12.19.8 Příklad implementace

Tato dílčí položka je informativní.

Tato dílčí část popisuje možnou implementaci převodů anonymních funkcí z hlediska jiných konstruktorů jazyka C#. Implementace popsaná zde je založena na stejných principech používaných komerčním kompilátorem jazyka C#, ale není to žádným způsobem pověřená implementace, ani není jedinou možnou implementací. Jenom stručně se zmiňuje o převodech na stromy výrazů, protože jejich přesná sémantika je mimo rozsah této specifikace.

Zbývající část tohoto dílčího seznamu obsahuje několik příkladů kódu, který obsahuje anonymní funkce s různými vlastnostmi. Pro každý příklad je k dispozici odpovídající překlad kódu, který používá pouze jiné konstruktory jazyka C#. V příkladech se předpokládá, že identifikátor D představuje následující typ delegáta:

public delegate void D();

Nejjednodušší forma anonymní funkce je ta, která nezachytí žádné vnější proměnné:

delegate void D();

class Test
{
    static void F()
    {
        D d = () => Console.WriteLine("test");
    }
}

Tuto instanci lze přeložit na instanci delegáta, která odkazuje na statickou metodu vygenerovanou kompilátorem, ve které je umístěn kód anonymní funkce:

delegate void D();

class Test
{
    static void F()
    {
        D d = new D(__Method1);
    }

    static void __Method1()
    {
        Console.WriteLine("test");
    }
}

V následujícím příkladu anonymní funkce odkazuje na členy instance this:

delegate void D();

class Test
{
    int x;

    void F()
    {
        D d = () => Console.WriteLine(x);
    }
}

To lze přeložit na metodu instance vygenerovanou kompilátorem obsahující kód anonymní funkce:

delegate void D();

class Test
{
   int x;

   void F()
   {
       D d = new D(__Method1);
   }

   void __Method1()
   {
       Console.WriteLine(x);
   }
}

V tomto příkladu anonymní funkce zachycuje místní proměnnou:

delegate void D();

class Test
{
    void F()
    {
        int y = 123;
        D d = () => Console.WriteLine(y);
    }
}

Životnost místní proměnné se teď musí rozšířit alespoň na dobu životnosti delegáta anonymní funkce. Toho lze dosáhnout "nahoistováním" místní proměnné do pole třídy generované kompilátorem. Vytvoření instance místní proměnné (§12.19.6.3) pak odpovídá vytvoření instance vygenerované třídy kompilátoru a přístup k místní proměnné odpovídá přístupu k poli v instanci generované třídy kompilátoru. Anonymní funkce se navíc stane metodou instance třídy generované kompilátorem:

delegate void D();

class Test
{
    void F()
    {
        __Locals1 __locals1 = new __Locals1();
        __locals1.y = 123;
        D d = new D(__locals1.__Method1);
    }

    class __Locals1
    {
        public int y;

        public void __Method1()
        {
            Console.WriteLine(y);
        }
    }
}

A konečně následující anonymní funkce zachycuje this a také dvě místní proměnné s různými životnostmi:

delegate void D();

class Test
{
   int x;

   void F()
   {
       int y = 123;
       for (int i = 0; i < 10; i++)
       {
           int z = i * 2;
           D d = () => Console.WriteLine(x + y + z);
       }
   }
}

Zde se vytvoří třída generovaná kompilátorem pro každý blok, ve kterém jsou místní hodnoty zachyceny tak, aby místní hodnoty v různých blocích mohly mít nezávislé životnosti. Instance __Locals2, kompilátor generované třídy pro vnitřní blok, obsahuje místní proměnnou z a pole, které odkazuje na instanci __Locals1. Instance __Locals1, kompilátorem vygenerovaná třída pro vnější blok, obsahuje místní proměnnou y a pole, které odkazuje na this uzavírajícího člena funkce. S těmito datovými strukturami je možné dosáhnout všech zachycených vnějších proměnných prostřednictvím instance __Local2a kód anonymní funkce lze tedy implementovat jako metodu instance této třídy.

delegate void D();

class Test
{
    int x;

    void F()
    {
        __Locals1 __locals1 = new __Locals1();
        __locals1.__this = this;
        __locals1.y = 123;
        for (int i = 0; i < 10; i++)
        {
            __Locals2 __locals2 = new __Locals2();
            __locals2.__locals1 = __locals1;
            __locals2.z = i * 2;
            D d = new D(__locals2.__Method1);
        }
    }

    class __Locals1
    {
        public Test __this;
        public int y;
    }

    class __Locals2
    {
        public __Locals1 __locals1;
        public int z;

        public void __Method1()
        {
            Console.WriteLine(__locals1.__this.x + __locals1.y + z);
        }
    }
}

Stejnou techniku, která se zde používá k zachycení místních proměnných, lze použít také při převodu anonymních funkcí na stromy výrazů: odkazy na objekty generované kompilátorem mohou být uloženy ve stromu výrazů a přístup k místním proměnným lze reprezentovat jako přístup k polím u těchto objektů. Výhodou tohoto přístupu je, že umožňuje sdílení "lifted" místních proměnných mezi delegáty a stromy výrazů.

Konec informativního textu.

12.20 Výrazy dotazů

12.20.1 Obecné

Výrazy dotazů poskytují jazykově integrovanou syntaxi pro dotazy, která je podobná relačním a hierarchickým dotazovacím jazykům, jako jsou SQL a XQuery.

query_expression
    : from_clause query_body
    ;

from_clause
    : 'from' type? identifier 'in' expression
    ;

query_body
    : query_body_clauses? select_or_group_clause query_continuation?
    ;

query_body_clauses
    : query_body_clause
    | query_body_clauses query_body_clause
    ;

query_body_clause
    : from_clause
    | let_clause
    | where_clause
    | join_clause
    | join_into_clause
    | orderby_clause
    ;

let_clause
    : 'let' identifier '=' expression
    ;

where_clause
    : 'where' boolean_expression
    ;

join_clause
    : 'join' type? identifier 'in' expression 'on' expression
      'equals' expression
    ;

join_into_clause
    : 'join' type? identifier 'in' expression 'on' expression
      'equals' expression 'into' identifier
    ;

orderby_clause
    : 'orderby' orderings
    ;

orderings
    : ordering (',' ordering)*
    ;

ordering
    : expression ordering_direction?
    ;

ordering_direction
    : 'ascending'
    | 'descending'
    ;

select_or_group_clause
    : select_clause
    | group_clause
    ;

select_clause
    : 'select' expression
    ;

group_clause
    : 'group' expression 'by' expression
    ;

query_continuation
    : 'into' identifier query_body
    ;

Výraz dotazu začíná klauzulí from a končí klauzulí select nebo group. Za počáteční klauzulí from může následovat nula nebo více from, let, where, join nebo orderby klauzulí. Každá klauzule from je generátor, který zavádí proměnnou rozsahu , která iteruje přes prvky sekvence . Každá klauzule let zavádí proměnnou rozsahu představující hodnotu vypočítanou pomocí předchozích proměnných rozsahu. Každá klauzule where je filtr, který z výsledku vylučuje položky. Každá klauzule join porovnává zadané klíče zdrojové sekvence s klíči jiné sekvence a poskytuje odpovídající páry. Každá klauzule orderby mění pořadí položek podle zadaných kritérií. Konečná select nebo group klauzule určuje tvar výsledku z hlediska proměnných rozsahu. Nakonec lze klauzuli into použít k „splice“ dotazů tím, že v následném dotazu zachází s výsledky jednoho dotazu jako s generátorem.

12.20.2 Nejednoznačnosti ve výrazech dotazů

Výrazy dotazu používají několik kontextových klíčových slov (§6.4.4): ascending, by, descending, equals, from, group, into, join, let, on, orderby, select a where.

Aby nedocházelo k nejednoznačnostem, které by mohly vzniknout z použití těchto identifikátorů jako klíčových slov i jednoduchých názvů, jsou tyto identifikátory považovány za klíčová slova kdekoli ve výrazu dotazu, pokud nejsou předponou "@" (§6.4.4) v takovém případě se považují za identifikátory. Pro tento účel je výraz dotazu libovolný výraz začínající výrazem "fromidentifikátor" následovaný libovolným tokenem kromě ";", "=" nebo ",".

12.20.3 Překlad výrazů dotazu

12.20.3.1 Obecné

Jazyk C# neurčí sémantiku spouštění výrazů dotazu. Výrazy dotazu se spíše překládají do vyvolání metod, které odpovídají vzoru výrazu dotazu (§12.20.4). Konkrétně se výrazy dotazu překládají do vyvolání metod pojmenovaných Where, Select, SelectMany, Join, GroupJoin, OrderBy, OrderByDescending, ThenBy, ThenByDescending, GroupBya Cast. Tyto metody mají mít určité podpisy a návratové typy, jak je popsáno v §12.20.4. Tyto metody mohou být metody instance objektu dotazované nebo rozšiřující metody, které jsou externí objektu. Tyto metody implementují skutečné vykonání dotazu.

Překlad dotazovacích výrazů na volání metod je syntaktické mapování, ke kterému dochází před provedením jakéhokoli přiřazení typu nebo řešení přetížení. Po překladu výrazů dotazu se výsledné vyvolání metody zpracovávají jako vyvolání běžných metod, což může zase odhalit chyby v čase kompilace. Mezi tyto chybové stavy patří metody, které neexistují, argumenty nesprávných typů a obecné metody, ve kterých se odvozování typu nezdaří.

Výraz dotazu se zpracuje opakovaným použitím následujících překladů, dokud nebude možné žádné další snížení. Překlady jsou uvedeny v pořadí použití: každý oddíl předpokládá, že překlady v předchozích oddílech byly provedeny vyčerpávajícím způsobem a po vyčerpání se oddíl později znovu nezobrazí při zpracování stejného výrazu dotazu.

Jedná se o chybu v době kompilace, kdy výraz dotazu zahrnuje přiřazení proměnné rozsahu nebo použití proměnné rozsahu jako argumentu pro odkaz nebo výstupní parametr.

Některé překlady vkládají proměnné rozsahu s transparentními identifikátory , označenými *. Jsou popsány dále v §12.20.3.8.

12.20.3.2 Výrazy dotazů s pokračováními

Výraz dotazu s pokračováním za textem dotazu

from «x1» in «e1» «b1» into «x2» «b2»

je přeloženo do

from «x2» in ( from «x1» in «e1» «b1» ) «b2»

Překlady v následujících částech předpokládají, že dotazy nemají žádné pokračování.

příklad: Příklad:

from c in customers
group c by c.Country into g
select new { Country = g.Key, CustCount = g.Count() }

je přeložen do:

from g in
   (from c in customers
   group c by c.Country)
select new { Country = g.Key, CustCount = g.Count() }

konečný překlad:

customers.
GroupBy(c => c.Country).
Select(g => new { Country = g.Key, CustCount = g.Count() })

konec příkladu

12.20.3.3 Explicitní typy proměnných rozsahu

Klauzule from, která explicitně určuje typ proměnné rozsahu

from «T» «x» in «e»

se přeloží na

from «x» in ( «e» ) . Cast < «T» > ( )

Klauzule join, která explicitně určuje typ proměnné rozsahu

join «T» «x» in «e» on «k1» equals «k2»

je přeloženo do

join «x» in ( «e» ) . Cast < «T» > ( ) on «k1» equals «k2»

Překlady v následujících částech předpokládají, že dotazy nemají žádné explicitní typy proměnných rozsahu.

Příklad: Příklad

from Customer c in customers
where c.City == "London"
select c

je přeloženo do

from c in (customers).Cast<Customer>()
where c.City == "London"
select c

jehož konečný překlad je

customers.
Cast<Customer>().
Where(c => c.City == "London")

konec příkladu

Poznámka: Explicitní typy proměnných rozsahu jsou užitečné pro dotazování kolekcí, které implementují neobecné IEnumerable rozhraní, ale ne obecné IEnumerable<T> rozhraní. V předchozím příkladu by to byl případ, kdy zákazníci byli typu ArrayList. koncová poznámka

12.20.3.4 Degenerované výrazy dotazu

Výraz dotazu ve formě

from «x» in «e» select «x»

je přeloženo do

( «e» ) . Select ( «x» => «x» )

Příklad: Příklad

from c in customers
select c

překládá se do

(customers).Select(c => c)

příklad koncový

Degenerační výraz dotazu je výraz, který triviálně vybere prvky zdroje.

Poznámka: Pozdější fáze překladu (§12.20.3.6 a §12.20.3.7) odstraňují degenerované dotazy zavedené jinými kroky překladu nahrazením jejich zdroje. Je však důležité zajistit, aby výsledek výrazu dotazu nikdy nebyl zdrojovým objektem samotným. V opačném případě může vrácení výsledku takového dotazu neúmyslně vystavit privátní data (např. pole elementů) volajícímu. Tento krok proto chrání degenerované dotazy napsané přímo ve zdrojovém kódu tím, že explicitně volá Select ve zdroji. Pak je až na implementátory Select a dalších operátorů dotazů, aby se zajistilo, že tyto metody nikdy nevracely samotný zdrojový objekt. koncová poznámka

12.20.3.5 From, let, where, join and orderby klauzule

Výraz dotazu s druhou klauzulí from následovanou klauzulí select

from «x1» in «e1»  
from «x2» in «e2»  
select «v»

se přeloží do

( «e1» ) . SelectMany( «x1» => «e2» , ( «x1» , «x2» ) => «v» )

Příklad: Příklad

from c in customers
from o in c.Orders
select new { c.Name, o.OrderID, o.Total }

překládá se do

(customers).
SelectMany(c => c.Orders,
(c,o) => new { c.Name, o.OrderID, o.Total }
)

ukončit příklad

Výraz dotazu s druhou klauzulí from následovanou textem dotazu Q obsahující neprázdnou sadu klauzulí textu dotazu:

from «x1» in «e1»
from «x2» in «e2»
Q

je přeloženo do

from * in («e1») . SelectMany( «x1» => «e2» ,
                              ( «x1» , «x2» ) => new { «x1» , «x2» } )
Q

Příklad: Příklad

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

se přeloží do

from * in (customers).
   SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

konečný překlad, který je

customers.
SelectMany(c => c.Orders, (c,o) => new { c, o }).
OrderByDescending(x => x.o.Total).
Select(x => new { x.c.Name, x.o.OrderID, x.o.Total })

kde x je identifikátor vygenerovaný kompilátorem, který je jinak neviditelný a nepřístupný.

konec příkladu

Výraz let spolu s předchozí klauzulí from:

from «x» in «e»  
let «y» = «f»  
...

je přeloženo do

from * in ( «e» ) . Select ( «x» => new { «x» , «y» = «f» } )  
...

Příklad: Příklad

from o in orders
let t = o.Details.Sum(d => d.UnitPrice * d.Quantity)
where t >= 1000
select new { o.OrderID, Total = t }

se přeloží do

from * in (orders).Select(
    o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
where t >= 1000
select new { o.OrderID, Total = t }

konečný překlad

orders
    .Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
    .Where(x => x.t >= 1000)
    .Select(x => new { x.o.OrderID, Total = x.t })

kde x je identifikátor vygenerovaný kompilátorem, který je jinak neviditelný a nepřístupný.

konec příkladu

Výraz where spolu s předchozí klauzulí from:

from «x» in «e»  
where «f»  
...

je přeloženo do

from «x» in ( «e» ) . Where ( «x» => «f» )  
...

Klauzule join bezprostředně následovaná klauzulí select

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
select «v»

se přeloží do

( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )

Příklad: Příklad

from c in customersh
join o in orders on c.CustomerID equals o.CustomerID
select new { c.Name, o.OrderDate, o.Total }

se přeloží do

(customers).Join(
   orders,
   c => c.CustomerID, o => o.CustomerID,
   (c, o) => new { c.Name, o.OrderDate, o.Total })

konec příkladu

Klauzule join následovaná klauzulí textu dotazu:

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
...

se přeloží do

from * in ( «e1» ) . Join(  
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })  
...

Klauzule join-into bezprostředně následovaná klauzulí select

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into «g»  
select «v»

překládá se do

( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
                     ( «x1» , «g» ) => «v» )

Klauzule join into následovaná klauzulí textu dotazu

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into *g»  
...

se přeloží do

from * in ( «e1» ) . GroupJoin(  
   «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => new { «x1» , «g» })
...

Příklad: Příklad

from c in customers
join o in orders on c.CustomerID equals o.CustomerID into co
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }

je přeloženo do

from * in (customers).GroupJoin(
    orders,
    c => c.CustomerID,
    o => o.CustomerID,
    (c, co) => new { c, co })
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }

konečný překlad, jehož je

customers
    .GroupJoin(
        orders,
        c => c.CustomerID,
        o => o.CustomerID,
        (c, co) => new { c, co })
    .Select(x => new { x, n = x.co.Count() })
    .Where(y => y.n >= 10)
    .Select(y => new { y.x.c.Name, OrderCount = y.n })

kde x a y jsou vygenerované identifikátory kompilátoru, které jsou jinak neviditelné a nepřístupné.

koncového příkladu

Klauzule orderby a její předchozí klauzule from:

from «x» in «e»  
orderby «k1» , «k2» , ... , «kn»  
...

je přeloženo do

from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...

Pokud klauzule ordering určuje sestupný ukazatel směru, vytvoří se místo toho vyvolání OrderByDescending nebo ThenByDescending.

Příklad: Příklad

from o in orders
orderby o.Customer.Name, o.Total descending
select o

má konečný překlad.

(orders)
    .OrderBy(o => o.Customer.Name)
    .ThenByDescending(o => o.Total)

ukončit příklad

Následující překlady předpokládají, že neexistují žádné let, where, join nebo orderby klauzule a ne více než jedna počáteční from klauzule v každém výrazu dotazu.

12.20.3.6 Klauzule Select

Výraz dotazu formuláře

from «x» in «e» select «v»

se přeloží do

( «e» ) . Select ( «x» => «v» )

s výjimkou «v» je identifikátor «x», překlad je jednoduše

( «e» )

Příklad: Příklad

from c in customers.Where(c => c.City == "London")
select c

je jednoduše přeložen do

(customers).Where(c => c.City == "London")

ukončit příklad

12.20.3.7 Skupinové klauzule

Klauzule group

from «x» in «e» group «v» by «k»

se přeloží do

( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )

Kromě případu, kdy je «v» identifikátor «x», je překlad

( «e» ) . GroupBy ( «x» => «k» )

Příklad: Příklad

from c in customers
group c.Name by c.Country

se přeloží do

(customers).GroupBy(c => c.Country, c => c.Name)

konec příkladu

12.20.3.8 Transparentní identifikátory

Některé překlady vkládají proměnné rozsahu s transparentními identifikátory označené *. Transparentní identifikátory existují pouze v přechodném kroku procesu překladu výrazů dotazu.

Když překlad dotazu vloží transparentní identifikátor, další kroky překladu rozšíří transparentní identifikátor do anonymních funkcí a inicializátorů anonymních objektů. V těchto kontextech mají transparentní identifikátory následující chování:

  • Pokud se transparentní identifikátor objeví jako parametr v anonymní funkci, členové přidruženého anonymního typu jsou automaticky v dosahu v těle anonymní funkce.
  • Pokud je člen s průhledným identifikátorem dostupný, členové tohoto člena jsou také dostupní.
  • Když se jako deklarátor člena v inicializátoru anonymního objektu vyskytuje transparentní identifikátor, zavádí člena s transparentním identifikátorem.

V krocích překladu popsaných výše jsou transparentní identifikátory vždy zavedeny společně s anonymními typy s záměrem zachycení více proměnných rozsahu jako členů jednoho objektu. Implementace jazyka C# umožňuje použít jiný mechanismus než anonymní typy k seskupení více proměnných rozsahu. Následující příklady překladu předpokládají, že se používají anonymní typy a ukazuje jeden z možných překladů transparentních identifikátorů.

Příklad: Příklad

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.Total }

se přeloží do

from * in (customers).SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.Total }

která je dále přeložena do

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(* => o.Total)
    .Select(\* => new { c.Name, o.Total })

které, pokud jsou průhledné identifikátory vymazány, je ekvivalentní

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(x => x.o.Total)
    .Select(x => new { x.c.Name, x.o.Total })

kde x je identifikátor vygenerovaný kompilátorem, který je jinak neviditelný a nepřístupný.

Příklad

from c in customers
join o in orders on c.CustomerID equals o.CustomerID
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

se přeloží do

from * in (customers).Join(
    orders,
    c => c.CustomerID,
    o => o.CustomerID,
    (c, o) => new { c, o })
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

který je dále omezen na

customers
    .Join(orders, c => c.CustomerID,
        o => o.CustomerID, (c, o) => new { c, o })
    .Join(details, * => o.OrderID, d => d.OrderID, (*, d) => new { *, d })
    .Join(products, * => d.ProductID, p => p.ProductID,
        (*, p) => new { c.Name, o.OrderDate, p.ProductName })

konečný překlad, jehož výsledkem je

customers
    .Join(orders, c => c.CustomerID,
        o => o.CustomerID, (c, o) => new { c, o })
    .Join(details, x => x.o.OrderID, d => d.OrderID, (x, d) => new { x, d })
    .Join(products, y => y.d.ProductID, p => p.ProductID,
        (y, p) => new { y.x.c.Name, y.x.o.OrderDate, p.ProductName })

kde x a y jsou identifikátory generované kompilátorem, které jsou jinak neviditelné a nepřístupné. konec příkladu

12.20.4 Vzor výrazu dotazu

Vzor výrazu dotazu vytvoří vzor metod, které typy mohou implementovat pro podporu výrazů dotazů.

Obecný typ C<T> podporuje model výrazů dotazu, pokud by jeho metody veřejného člena a veřejně přístupné rozšiřující metody mohly být nahrazeny následující definicí třídy. Členy a přístupné metody extenson se označují jako "tvar" obecného typu C<T>. Obecný typ se používá k ilustraci správných vztahů mezi parametry a návratovými typy, ale je možné implementovat i vzor pro ne generické typy.

delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);

class C
{
    public C<T> Cast<T>() { ... }
}

class C<T> : C
{
    public C<T> Where(Func<T,bool> predicate) { ... }
    public C<U> Select<U>(Func<T,U> selector) { ... }
    public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
        Func<T,U,V> resultSelector) { ... }
    public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,U,V> resultSelector) { ... }
    public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector) { ... }
    public O<T> OrderBy<K>(Func<T,K> keySelector) { ... }
    public O<T> OrderByDescending<K>(Func<T,K> keySelector) { ... }
    public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector) { ... }
    public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
        Func<T,E> elementSelector) { ... }
}

class O<T> : C<T>
{
    public O<T> ThenBy<K>(Func<T,K> keySelector) { ... }
    public O<T> ThenByDescending<K>(Func<T,K> keySelector) { ... }
}

class G<K,T> : C<T>
{
    public K Key { get; }
}

Výše uvedené metody používají obecné typy delegátů Func<T1, R> a Func<T1, T2, R>, ale mohly by stejně dobře použít jiné typy delegáta nebo stromu výrazů se stejnými relacemi v parametrech a návratových typech.

Poznámka: Doporučený vztah mezi C<T> a O<T>, který zajišťuje, že metody ThenBy a ThenByDescending jsou k dispozici pouze na základě výsledku OrderBy nebo OrderByDescending. koncová poznámka

Poznámka: Doporučený tvar výsledku GroupBy– posloupnost sekvencí, kde má každá vnitřní sekvence další vlastnost Key. koncová poznámka

Poznámka: Vzhledem k tomu, že výrazy dotazu se překládají na vyvolání metod pomocí syntaktického mapování, mají typy značnou flexibilitu při implementaci libovolného nebo všech vzorů výrazů dotazu. Například metody vzoru lze implementovat jako metody instance nebo jako rozšiřující metody, protože tyto dvě mají stejnou syntaxi vyvolání a metody mohou požadovat delegáty nebo stromy výrazů, protože anonymní funkce jsou převoditelné na obě. Typy, které implementují pouze některé vzory výrazů dotazu, podporují pouze překlady těchto výrazů, které lze namapovat na metody, jež tento typ podporuje. koncová poznámka

Poznámka: Obor názvů System.Linq poskytuje implementaci vzoru výrazu dotazu pro libovolný typ, který implementuje rozhraní System.Collections.Generic.IEnumerable<T>. koncová poznámka

12.21 Operátory přiřazení

12.21.1 Obecné

Všechny operátory přiřazení kromě jednoho z operátorů přiřazení přiřadí novou hodnotu proměnné, vlastnosti, události nebo prvku indexeru. Výjimka, = ref, přiřadí referenci na proměnnou (§9.5) referenční proměnné (§9.7).

assignment
    : unary_expression assignment_operator expression
    ;

assignment_operator
    : '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<='
    | right_shift_assignment
    ;

Levý operand přiřazení musí být výraz klasifikovaný jako proměnná nebo, s výjimkou = ref, přístup k vlastnosti, indexeru, události nebo n-tice. Výraz deklarace není přímo povolen jako levá operanda, ale může se objevit jako krok při evaluaci dekonstruujícího přiřazení.

Operátor = se nazývá jednoduchý operátor přiřazení . Přiřadí hodnotu nebo hodnoty pravého operandu proměnné, vlastnosti, prvku indexeru nebo prvkům n-tice určeným levým operandem. Levý operand operátoru jednoduchého přiřazení nesmí být přístupem k události (s výjimkou případů popsaných v §15.8.2). Operátor jednoduchého přiřazení je popsán v §12.21.2.

Operátor = ref se nazývá operátor přiřazení odkazu . Určuje pravý operand, který je variable_reference (§9.5), jako referent referenční proměnné, kterou určuje levý operand. Operátor přiřazení odkazu je popsán v §12.21.3.

Operátory přiřazení, kromě operátorů = a = ref, se nazývají složené operátory přiřazení . Tyto operátory provádějí uvedenou operaci na dvou operandech a potom přiřazují výslednou hodnotu proměnné, vlastnosti nebo prvku indexeru zadanému levým operandem. Operátory složeného přiřazení jsou popsány v §12.21.4.

Operátory += a -= s výrazem pro přístup k události jako levý operand se nazývají operátory přiřazení událostí . Žádný jiný operátor přiřazení není platný s přístupem k události jako levým operandem. Operátory přiřazení událostí jsou popsány v §12.21.5.

Operátory přiřazení mají pravoasociativní povahu, což znamená, že operace jsou seskupovány zprava doleva.

Příklad: Výraz ve tvaru a = b = c se vyhodnotí jako a = (b = c). konec příkladu

12.21.2 Jednoduché přiřazení

Operátor = se nazývá jednoduchý operátor přiřazení.

Je-li levý operand jednoduchého přiřazení ve tvaru E.P nebo E[Ei], a E má kompilovaný typ dynamic, pak je přiřazení dynamicky vázáno (§12.3.3). V tomto případě je typ výrazu přiřazení určen za kompilace jako dynamica rozlišení, popsané níže, bude provedeno za běhu na základě typu E. Je-li levý operand formuláře E[Ei], kde alespoň jeden prvek Eidynamictyp kompilace a typ doby kompilace E není pole, výsledný přístup indexeru je dynamicky vázán, ale s omezenou kontrolou času kompilace (§12.6.5).

Jednoduché přiřazení, kde je levý operand klasifikován jako n-tice, se také nazývá dekonstrukční přiřazení. Pokud některý z elementů řazené kolekce členů levého operandu má název elementu, dojde k chybě v době kompilace. Pokud některý z prvků řazené kolekce členů levého operandu je declaration_expression a jakýkoli jiný prvek není declaration_expression nebo jednoduchý zahození, dojde k chybě v době kompilace.

Typ jednoduchého přiřazení x = y je typ přiřazení k x z y, který je určován rekurzivním způsobem následovně:

  • Je-li x výraz řazené kolekce členů (x1, ..., xn)a y lze dekonstruovat na výraz řazené kolekce členů (y1, ..., yn) s prvky n (§12.7) a každé přiřazení xiyi má typ Ti, pak přiřazení má typ (T1, ..., Tn).
  • Jinak pokud je x klasifikována jako proměnná, proměnná není readonly, x má typ Ta y má implicitní převod na T, pak přiřazení má typ T.
  • V opačném případě je x klasifikována jako implicitně typovaná proměnná (tj. implicitně zadaný výraz deklarace) a y má typ T, odvozený typ proměnné je Ta přiřazení má typ T.
  • Jinak pokud je x klasifikován jako vlastnost nebo přístup indexeru, a má přístupný nastavovací přístupový člen, x má typ Ta y má implicitní převod na T, pak přiřazení má typ T.
  • V opačném případě není přiřazení platné a dojde k chybě při vyhodnocování času vazby.

Zpracování za běhu jednoduchého přiřazení formuláře x = y s typem T se provádí jako přiřazení k xy s typem T, který se skládá z následujících rekurzivních kroků:

  • x se vyhodnotí, pokud to ještě nebylo.
  • Pokud je x klasifikována jako proměnná, y se vyhodnotí a v případě potřeby se převede na T prostřednictvím implicitního převodu (§10,2).
    • Pokud je proměnná daná x prvkem pole odkazového typu, provede se kontrola za běhu, která zajistí, že hodnota vypočítaná pro y je kompatibilní s instancí pole, jehož x je prvkem. Kontrola proběhne úspěšně, pokud y je null, nebo pokud implicitní převod odkazu (§10.2.8) existuje z typu instance odkazované y na skutečný typ prvku instance pole obsahující x. V opačném případě se vyvolá System.ArrayTypeMismatchException.
    • Hodnota, která vznikla vyhodnocením a převodem y, je uložena na umístění, které je dáno vyhodnocením x, a je předána jako výsledek přiřazení.
  • Pokud je x klasifikován jako vlastnost nebo přístup indexeru:
    • y se vyhodnotí a v případě potřeby se převede na T prostřednictvím implicitního převodu (§10,2).
    • Sada přístupového objektu x je vyvolána s hodnotou, která je výsledkem vyhodnocení a převodu y jako argument hodnoty.
    • Hodnota vyplývající z vyhodnocení a převodu y je výsledkem přiřazení.
  • Pokud je x klasifikován jako n-tice (x1, ..., xn) s aritou n:
    • y se dekonstruktivně konstruuje s prvky n na n-ticový výraz e.
    • Výsledná řazená kolekce členů t se vytvoří převodem e na T pomocí implicitního převodu řazené kolekce členů.
    • pro každou xi zleva doprava se provede přiřazení t.Itemi do xi s tím rozdílem, že xi se znovu nevyhodnocují.
    • t je výsledkem přiřazení.

Poznámka: Pokud je typ času kompilace xdynamic a existuje implicitní převod z typu času kompilace y na dynamic, nevyžaduje se žádné řešení za běhu. koncová poznámka

Poznámka: Pravidla společné odchylky matice (§17.6) umožňují, aby hodnota typu matice A[] byla odkazem na instanci typu matice B[], pokud existuje implicitní převod odkazu z B na A. Vzhledem k těmto pravidlům je přiřazení prvku pole reference_type vyžaduje kontrolu za běhu, která zajistí, že přiřazená hodnota je kompatibilní s instancí pole. V příkladu

string[] sa = new string[10];
object[] oa = sa;
oa[0] = null;              // OK
oa[1] = "Hello";           // OK
oa[2] = new ArrayList();   // ArrayTypeMismatchException

Poslední přiřazení způsobí vyvolání System.ArrayTypeMismatchException, protože odkaz na ArrayList nelze uložit do prvku string[].

koncová poznámka

Pokud je vlastnost nebo indexer deklarovaný v struct_type cílem přiřazení, výraz instance přidružený k vlastnosti nebo přístupu indexeru se klasifikuje jako proměnná. Pokud je výraz instance klasifikován jako hodnota, dojde k chybě vyhodnocení času vazby.

Poznámka: Vzhledem k §12.8.7platí stejné pravidlo i pro pole. koncová poznámka

Příklad: Vzhledem k deklaracím:

struct Point
{
   int x, y;

   public Point(int x, int y)
   {
      this.x = x;
      this.y = y;
   }

   public int X
   {
      get { return x; }
      set { x = value; }
   }

   public int Y {
      get { return y; }
      set { y = value; }
   }
}

struct Rectangle
{
    Point a, b;

    public Rectangle(Point a, Point b)
    {
        this.a = a;
        this.b = b;
    }

    public Point A
    {
        get { return a; }
        set { a = value; }
    }

    public Point B
    {
        get { return b; }
        set { b = value; }
    }
}

v příkladu

Point p = new Point();
p.X = 100;
p.Y = 100;
Rectangle r = new Rectangle();
r.A = new Point(10, 10);
r.B = p;

přiřazení p.X, p.Y, r.Aa r.B jsou povolená, protože p a r jsou proměnné. V příkladu však

Rectangle r = new Rectangle();
r.A.X = 10;
r.A.Y = 10;
r.B.X = 100;
r.B.Y = 100;

Přiřazení jsou všechna neplatná, protože r.A a r.B nejsou proměnné.

konec příkladu

12.21.3 Přiřazení odkazu

Operátor = ref je známý jako operátor přiřazení ref.

Levý operand musí být výraz, který je vázán na referenční proměnnou (§9.7), parametr odkazu (jiný než this), výstupní parametr nebo vstupní parametr. Pravý operand musí být výraz, který poskytuje variable_reference označující hodnotu stejného typu jako levý operand.

Jedná se o chybu doby kompilace, pokud je kontext ref-safe-context (§9.7.2) levého operandu širší než kontext ref-safe-kontext pravého operandu.

Pravý operand musí být jistě přiřazen v okamžiku přiřazování reference.

Když levý operand vytvoří vazbu s výstupním parametrem, jedná se o chybu, pokud tento výstupní parametr nebyl určitě přiřazen na začátku operátoru přiřazení s ref.

Je-li levý operand zapisovatelným odkazem (tj. označuje cokoli jiného než ref readonly místní nebo vstupní parametr), bude pravý operand zapisovatelným variable_reference. Pokud je pravá proměnná operandu zapisovatelná, může být levý operand referencí, která je zapisovatelná nebo jen pro čtení.

Operace dělá z levého operandu alias pravé proměnné operandu. Alias může být jen pro čtení, i když je správná proměnná operandu zapisovatelná.

Operátor přiřazení ref poskytuje variable_reference přiřazeného typu. Je zapisovatelný, pokud je levý operand zapisovatelný.

Operátor přiřazení odkazu nepřečte umístění úložiště, na které odkazuje správný operand.

příklad: Tady je několik příkladů použití = ref:

public static int M1() { ... }
public static ref int M2() { ... }
public static ref uint M2u() { ... }
public static ref readonly int M3() { ... }
public static void Test()
{
int v = 42;
ref int r1 = ref v; // OK, r1 refers to v, which has value 42
r1 = ref M1();      // Error; M1 returns a value, not a reference
r1 = ref M2();      // OK; makes an alias
r1 = ref M2u();     // Error; lhs and rhs have different types
r1 = ref M3();    // error; M3 returns a ref readonly, which r1 cannot honor
ref readonly int r2 = ref v; // OK; make readonly alias to ref
r2 = ref M2();      // OK; makes an alias, adding read-only protection
r2 = ref M3();      // OK; makes an alias and honors the read-only
r2 = ref (r1 = ref M2());  // OK; r1 is an alias to a writable variable,
              // r2 is an alias (with read-only access) to the same variable
}

koncového příkladu

Poznámka: Při čtení kódu pomocí operátoru = ref může být lákavé číst ref část jako součást operandu. To je obzvláště matoucí, pokud je operand podmíněným výrazem ?:. Například při čtení ref int a = ref b ? ref x : ref y; je důležité to interpretovat jako že = ref je operátor a b ? ref x : ref y je pravý operand: ref int a = ref (b ? ref x : ref y);. Důležité je, že výraz ref b je není součástí tohoto příkazu, i když se může na první pohled objevit. koncová poznámka

12.21.4 Složené přiřazení

Je-li levý operand složeného přiřazení ve tvaru E.P nebo E[Ei], kde E má kompilaci časový typ dynamic, pak je přiřazení dynamicky vázáno (§12.3.3). V tomto případě je typ kompilátoru výrazu přiřazení dynamica řešení popsané níže proběhne za běhu na základě typu E. Je-li levý operand ve formě E[Ei], kde alespoň jeden prvek Ei má typ kompilace dynamic, a typ kompilace E není pole, výsledný přístup indexeru je dynamicky vázán, ale s omezenou kontrolou při kompilaci (§12.6.5).

Operace formuláře x «op»= y je zpracována použitím rozlišení přetížení binárního operátoru (§12.4.5) jako by operace byla zapsána x «op» y. Potom

  • Pokud je návratový typ vybraného operátoru implicitně konvertibilní na typ x, operace se vyhodnotí jako x = x «op» ys tím rozdílem, že x se vyhodnotí pouze jednou.
  • V opačném případě, pokud je vybraný operátor předdefinovaným operátorem, je-li návratový typ vybraného operátoru explicitně konvertibilní na typ x a pokud je y implicitně konvertibilní na typ x nebo operátor je operátor směny, operace se vyhodnotí jako x = (T)(x «op» y), kde T je typ x, kromě toho, že x se vyhodnotí pouze jednou.
  • V opačném případě je složené přiřazení neplatné a dojde k chybě určení času vazby.

Pojem "vyhodnocen pouze jednou" znamená, že při vyhodnocování x «op» yse výsledky všech základních výrazů x dočasně uloží a pak znovu použijí při provádění přiřazení k x.

Příklad: V A()[B()] += C()přiřazení, kde A je metoda vracející int[]a B a C jsou metody vracející int, metody jsou vyvolány pouze jednou, v pořadí A, B, C. konec příkladu

Pokud je levý operand složeného přiřazení přístupem k vlastnosti nebo indexeru, musí mít tato vlastnost nebo indexer přístupový prvek get i set. Pokud tomu tak není, dojde k chybě při určování doby vazby.

Druhé pravidlo výše umožňuje vyhodnotit x «op»= y jako x = (T)(x «op» y) v určitých kontextech. Pravidlo existuje tak, aby předdefinované operátory bylo možné použít jako složené operátory, pokud je levý operand typu sbyte, byte, short, ushortnebo char. I když jsou oba argumenty jednoho z těchto typů, předdefinované operátory vytvoří výsledek typu int, jak je popsáno v §12.4.7.3. Bez přetypování by tedy nebylo možné přiřadit výsledek levému operandu.

Intuitivním účinkem pravidla pro předdefinované operátory je jednoduše to, že x «op»= y je povoleno, pokud jsou povoleny obě x «op» y i x = y.

příklad: V následujícím kódu

byte b = 0;
char ch = '\0';
int i = 0;
b += 1;           // OK
b += 1000;        // Error, b = 1000 not permitted
b += i;           // Error, b = i not permitted
b += (byte)i;     // OK
ch += 1;          // Error, ch = 1 not permitted
ch += (char)1;    // OK

intuitivním důvodem každé chyby je, že odpovídající jednoduché přiřazení by také bylo chybou.

konec příkladu

Poznámka: To také znamená, že operace složeného přiřazení podporují zvedané operátory. Vzhledem k tomu, že složené přiřazení x «op»= y je vyhodnoceno jako x = x «op» y nebo x = (T)(x «op» y), pravidla vyhodnocení implicitně pokrývají operátory lifted. koncová poznámka

12.21.5 Přiřazení události

Pokud je levý operand operátoru a += or -= klasifikovaný jako přístup k události, vyhodnotí se výraz následujícím způsobem:

  • Vyhodnocuje se, pokud existuje, výraz instance pro přístup k události.
  • Pravý operand operátoru += nebo -= se vyhodnotí, a v případě potřeby se převede na typ levého operandu pomocí implicitní konverze (§10.2).
  • Je vyvolán přístupový člen události se seznamem argumentů, který se skládá z hodnoty vypočítané v předchozím kroku. Pokud byl operátor +=, je vyvolán přístupový objekt pro přidání; pokud byl operátor -=, je vyvolán přístupový objekt pro odstranění.

Výraz přiřazení události nevyvolá hodnotu. Výraz přiřazení události je tedy platný pouze v kontextu statement_expression (§13.7).

Výraz 12.22

Výraz je non_assignment_expression nebo přiřazení.

expression
    : non_assignment_expression
    | assignment
    ;

non_assignment_expression
    : declaration_expression
    | conditional_expression
    | lambda_expression
    | query_expression
    ;

12.23 Konstantní výrazy

Konstantní výraz je výraz, který se v době kompilace plně vyhodnotí.

constant_expression
    : expression
    ;

Konstantní výraz musí mít hodnotu null nebo jeden z následujících typů:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string;
  • výčtový typ; nebo
  • výchozí hodnota výrazu (§12.8.21) pro referenční typ.

Ve výrazech konstant jsou povoleny pouze následující konstrukce:

  • Literály (včetně literálu null).
  • Odkazy na členy tříd a struktur typů const.
  • Odkazy na členy typů výčtu.
  • Odkazy na místní konstanty.
  • Závorkované podvýrazy, které jsou samy o sobě konstantními výrazy.
  • Přetypování výrazů
  • výrazy checked a unchecked.
  • nameof výrazy.
  • Předdefinované +, -, ! (logická negace) a ~ unární operátory.
  • Předdefinované +, -, *, /, %, <<, >>, &, |, ^, &&, ||, ==, !=, <, >, <=a >= binární operátory.
  • Podmíněný operátor ?:.
  • Operátor ! odpouštějící hodnotu null (§12.8.9).
  • Výrazy sizeof, za předpokladu, že nespravovaný typ je jedním z typů uvedených v §23.6.9 , pro které sizeof vrací konstantní hodnotu.
  • Výchozí výrazy hodnot za předpokladu, že typ je jedním z výše uvedených typů.

V konstantních výrazech jsou povoleny následující převody:

  • Převody identit
  • Číselné převody
  • Výčtové převody
  • Převody konstantních výrazů
  • Implicitní a explicitní převody odkazů za předpokladu, že zdrojem převodů je konstantní výraz, který se vyhodnotí jako hodnota null.

Poznámka: Jiné převody, včetně krabicování, rozbalení a implicitních převodů odkazů beznull hodnot, nejsou ve výrazech konstanty povoleny. koncová poznámka

příklad: V následujícím kódu

class C
{
    const object i = 5;         // error: boxing conversion not permitted
    const object str = "hello"; // error: implicit reference conversion
}

inicializace i je chyba, protože je vyžadován převod boxingu. Inicializace str je chyba, protože je vyžadován implicitní převod odkazu z hodnoty, která nenínull.

konec příkladu

Kdykoli výraz splňuje výše uvedené požadavky, výraz se vyhodnotí v době kompilace. To platí i v případě, že výraz je dílčí výraz většího výrazu, který obsahuje nekontinutní konstrukce.

Vyhodnocení doby kompilace konstantních výrazů používá stejná pravidla jako vyhodnocení ne constantních výrazů, s tím rozdílem, že pokud by vyhodnocení za běhu vyvolalo výjimku, vyhodnocení v době kompilace způsobí, že dojde k chybě kompilace.

Pokud není konstantní výraz explicitně umístěn v kontextu unchecked, přetečení, ke kterým dochází v aritmetických operacích a převodech celočíselného typu během vyhodnocení výrazu v době kompilace, vždy způsobují chyby v době kompilace (§12.8.20).

Konstantní výrazy jsou vyžadovány v kontextech uvedených níže a to je uvedeno v gramatikě pomocí constant_expression. V těchto kontextech dojde k chybě v době kompilace, pokud výraz nelze plně vyhodnotit v době kompilace.

  • Konstantní deklarace (§15.4)
  • Prohlášení členů výčtu (§19.4)
  • Výchozí argumenty seznamů parametrů (§15.6.2)
  • case popisky switch prohlášení (§13.8.3).
  • goto case prohlášení (§13.10.4)
  • Délky dimenzí ve výrazu pro vytvoření pole (§12.8.17.5), který obsahuje inicializátor.
  • Atributy (§22)
  • V konstantní_vzor (§ 11.2.3)

Implicitní převod konstantního výrazu (§10.2.11) umožňuje, aby konstantní výraz typu int byl převeden na sbyte, byte, short, ushort, uintnebo ulong, za předpokladu, že hodnota konstantního výrazu je v rozsahu cílového typu.

12.24 Logické výrazy

boolean_expression je výraz, který poskytuje výsledek typu bool; buď přímo, nebo prostřednictvím použití operator true v určitých kontextech, jak je uvedeno níže:

boolean_expression
    : expression
    ;

Řídící podmínkový výraz u if_statement (§13.8.2), while_statement (§13.9.2), do_statement (§13.9.3) nebo for_statement (§13.9.4) je boolean_expression. Řídicí podmíněný výraz operátoru ?: (§12.18) se řídí stejnými pravidly jako boolean_expression, ale z důvodů přednosti operátoru je klasifikován jako null_coalescing_expression.

Aby bylo možné vytvořit hodnotu typu bool, je nutné boolean_expressionE následujícím způsobem:

  • Pokud je E implicitně konvertibilní na bool pak se použije implicitní převod za běhu.
  • V opačném případě se rozlišení přetížení unárního operátoru (§12.4.4) používá k nalezení jedinečné nejlepší implementace operator true na Ea tato implementace se použije za běhu.
  • Pokud se nenajde žádný takový operátor, dojde k chybě při určování vázacího času.