Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
6.1 Programy
Program jazyka C# se skládá z jednoho nebo více zdrojových souborů, označovaných formálně jako kompilační jednotky (§14.2). I když kompilační jednotka může mít 1:1 korespondenci se souborem v systému souborů, taková korespondence se nevyžaduje.
Koncepčně řečeno, program je zkompilován pomocí tří kroků:
- Transformace, která převádí soubor z určitého znakového schématu a schématu kódování na posloupnost znaků Unicode.
- Lexikální analýza, která překládá datový proud vstupních znaků Unicode do datového proudu tokenů.
- Syntaktická analýza, která překládá datový proud tokenů do spustitelného kódu.
Odpovídající implementace přijímají kompilační jednotky Unicode kódované pomocí formátu kódování UTF-8 (jak je definováno standardem Unicode) a transformují je do posloupnosti znaků Unicode. Implementace se můžou rozhodnout přijímat a transformovat další schémata kódování znaků (například UTF-16, UTF-32 nebo jiná mapování znaků než Unicode).
Poznámka: Zpracování znaku Unicode NULL (U+0000) je definováno implementací. Důrazně doporučujeme, aby vývojáři nepoužívat tento znak ve zdrojovém kódu kvůli přenositelnosti i čitelnosti. Pokud je znak požadován v rámci znaku nebo řetězcového literálu, řídicí sekvence
\0
nebo\u0000
mohou být použity místo toho. koncová poznámka
Poznámka: Je nad rámec této specifikace definovat, jak může být soubor používající jinou reprezentaci znaků než Unicode transformován do posloupnosti znaků Unicode. Během takové transformace se však doporučuje přeložit obvyklý znak pro oddělení řádku (nebo sekvenci) v jiné znakové sadě na dvouznakovou sekvenci složenou ze znaku Unicode pro návrat vozíku (U+000D) následovaného znakem Unicode pro posun řádku (U+000A). Ve většině případů tato transformace nebude mít žádné viditelné účinky; nicméně bude mít vliv na interpretaci doslovných řetězcových literálů (§6.4.5.6). Účelem tohoto doporučení je umožnit doslovné řetězcové literály vytvořit stejnou posloupnost znaků, když se jeho kompilační jednotka přesune mezi systémy, které podporují různé znakové sady, zejména ty, které používají různé sekvence znaků pro oddělení řádků. koncová poznámka
6.2 Gramatiky
6.2.1 Obecné
Tato specifikace představuje syntaxi programovacího jazyka C# pomocí dvou gramatik. Lexikální gramatika (§6.2.3) definuje, jak se znaky Unicode kombinují a tvoří ukončovací znaky řádků, prázdné znaky, komentáře, tokeny a direktivy předběžného zpracování. Syntaktická gramatika (§6.2.4) definuje, jak se tokeny vyplývající z lexikální gramatiky kombinují a tvoří programy jazyka C#.
Všechny terminálové znaky se považují za odpovídající znak Unicode z rozsahu U+0020 až U+007F, a ne jako všechny podobné znaky z jiných oblastí znaků Unicode.
6.2.2 Zápis gramatiky
Lexikální a syntaktické gramatiky jsou uvedeny v nástroji ANTLR ve formátu Extended Backus-Naur.
I když se používá zápis ANTLR, tato specifikace neobsahuje úplnou "referenční gramatiku" připravenou pro ANTLR pro jazyk C#; psaní lexeru a analyzátoru, ať už ručně, nebo pomocí nástroje, jako je ANTLR, je mimo rozsah specifikace jazyka. S touto kvalifikací se tato specifikace snaží minimalizovat mezeru mezi specifikovanou gramatikou a gramatikou požadovanou pro vytvoření lexeru a parseru v ANTLR.
ANTLR rozlišuje mezi lexikální a syntaktickou gramatikou ve svém zápisu tím, že lexikální pravidla začínají velkým písmenem a syntaktická pravidla, nazývaná ANTLR jako parser, malým písmenem.
Poznámka: Lexikální gramatika jazyka C# (§6.2.3) a syntaktická gramatika (§6.2.4) nejsou přesně v souladu s rozdělením ANTLR do lexikálních a parserových grammerů. Tato malá neshoda znamená, že se při zadávání lexikální gramatiky jazyka C# používají některá pravidla analyzátoru ANTLR. koncová poznámka
6.2.3 Lexikální gramatika
Lexikální gramatika jazyka C# je uvedena v §6.3, §6.4 a §6.5. Terminálové symboly lexikální gramatiky jsou znaky znakové sady Unicode a lexikální gramatika určuje, jak se znaky kombinují s tvarem tokenů (§6.4), prázdné znaky (§6.3.4), komentáře (§6.3.3) a direktivy předběžného zpracování (§6.5).
Mnoho symbolů terminálu syntaktické gramatiky není definováno explicitně jako tokeny v lexikální gramatikě. Spíše je výhodou chování ANTLR, že literální řetězce v gramatikě jsou extrahovány jako implicitní lexikální tokeny; to umožňuje, aby klíčová slova, operátory atd. byly reprezentovány v gramatikě jejich literálovým vyjádřením, nikoli názvem tokenu.
Každá kompilační jednotka v programu jazyka C# musí odpovídat vstupní výrobě lexikální gramatiky (§6.3.1).
6.2.4 Syntaktická gramatika
Syntaktická gramatika jazyka C# je uvedena v klauzulích, podkapitolách a přílohách, které následují za touto podkapitolou. Symboly terminálu syntaktické gramatiky jsou tokeny definované explicitně lexikální gramatikou a implicitně literálními řetězci v samotné gramatikě (§6.2.3). Syntaktická gramatika určuje, jak se tokeny kombinují pro tvorbu programů v jazyce C#.
Každá kompilační jednotka v programu jazyka C# musí odpovídat produkci compilation_unit (§14.2) syntaktické gramatiky.
6.2.5 Gramatické nejednoznačnosti
Produkce pro:
- simple_name (§12.8.4),
- member_access (§12.8.7),
- null_conditional_member_access (§12.8.8),
- závislý_přístup (§12.8.8)
- base_access (§ 12.8.15) a
- pointer_member_access (§23.6.3);
("jednoznačné produkce") mohou způsobit nejednoznačnosti v gramatice výrazů.
Tyto produkce se vyskytují v kontextech, kde může hodnota vyskytovat ve výrazu, a mají jednu nebo více alternativ, které končí gramatikou "identifier type_argument_list?
". Jedná se o nepovinný type_argument_list, který vede k možné nejednoznačnosti.
Příklad: Výrok:
F(G<A, B>(7));
lze interpretovat jako volání
F
se dvěma argumentyG < A
aB > (7)
. Alternativně je možné ji interpretovat jako voláníF
s jedním argumentem, což je volání obecné metodyG
se dvěma argumenty typu a jedním pravidelným argumentem.konec příkladu
Je-li možné analyzovat posloupnost tokenů v kontextu jako jednu z jednoznačných produkcí, včetně volitelné type_argument_list (§8.4.2), pak bude token nacházející se bezprostředně po závěrečném >
tokenu posouzen a pokud je:
- jeden z
( ) ] } : ; , . ? == != | ^ && || & [
; nebo - jeden z relačních operátorů
< <= >= is as
; nebo - kontextové klíčové slovo dotazu, které se zobrazí uvnitř výrazu dotazu.
type_argument_list se zachovají jako součást jednoznačné produkce a veškeré další možné výklady posloupnosti tokenů budou zahozeny. Jinak se tokeny analyzované jako type_argument_list nepovažují za součást nejednoznačné produkce, i když neexistují žádné další možné analýzy těchto tokenů.
Poznámka: Tato pravidla rozlišení se nepoužijí při analýze jiných produkcí, i když podobně končí na "\
identifier type_argument_list?
"; tyto produkce se parsují jako normální. Příklady: namespace_or_type_name (§7.8); named_entity (§12.8.23); null_conditional_projection_initializer (§12.8.8); a qualified_alias_member (§14.8.1). koncová poznámka
Příklad: Výrok:
F(G<A, B>(7));
bude podle tohoto pravidla interpretována jako volání
F
s jedním argumentem, což je volání obecné metodyG
se dvěma argumenty typu a jedním pravidelným argumentem. ProhlášeníF(G<A, B>7); F(G<A, B>>7);
každý z nich bude interpretován jako volání
F
se dvěma argumenty. Prohlášeníx = F<A> + y;
bude interpretována jako operátor menší než, operátor větší než a unární operátor plus, jako by byl příkaz napsán
x = (F < A) > (+y)
, místo jako simple_name s type_argument_list následovaný binárním operátorem plus. V prohlášeníx = y is C<T> && z;
tokeny
C<T>
se interpretují jako namespace_or_type_name s type_argument_list kvůli přítomnosti nejednoznačného tokenu&&
za type_argument_list.Výraz
(A < B, C > D)
je uspořádaná dvojice se dvěma prvky, z nichž každý je porovnání.Výraz
(A<B,C> D, E)
je n-tice se dvěma prvky, z nichž první je deklarativní výraz.Vyvolání
M(A < B, C > D, E)
má tři argumenty.Vyvolání
M(out A<B,C> D, E)
má dva argumenty, z nichž první jeout
deklarace.
e is A<B> C
Výraz používá vzor deklarace.Štítek
case A<B> C:
případu používá deklarativní vzor.konec příkladu
Při rozpoznání relational_expression (§12.12.1), pokud jsou použitelné alternativy "relational_expressionis
typ" a "relational_expressionis
vzor" a typ se přeloží na přístupný typ, pak se zvolí alternativa "relational_expressionis
typ".
6.3 Lexikální analýza
6.3.1 Obecné
Z důvodu usnadnění definuje lexikální gramatika a odkazuje na následující pojmenované tokeny lexeru:
DEFAULT : 'default' ;
NULL : 'null' ;
TRUE : 'true' ;
FALSE : 'false' ;
ASTERISK : '*' ;
SLASH : '/' ;
I když se jedná o pravidla lexeru, jsou tyto názvy napsané ve všech velkých písmenech, aby se odlišily od běžných názvů pravidel lexeru.
Poznámka: Tato pravidla pohodlí jsou výjimky z obvyklé praxe nezadávat explicitní názvy tokenů pro tokeny definované literálovými řetězci. koncová poznámka
Vstupní produkce definuje lexikální strukturu kompilační jednotky jazyka C#.
input
: input_section?
;
input_section
: input_section_part+
;
input_section_part
: input_element* New_Line
| PP_Directive
;
input_element
: Whitespace
| Comment
| token
;
Poznámka: Výše uvedená gramatika je popsaná pravidly analýzy ANTLR, definuje lexikální strukturu kompilační jednotky jazyka C# a ne lexikální tokeny. koncová poznámka
Pět základních prvků tvoří lexikální strukturu kompilační jednotky jazyka C#: Ukončovací čáry (§6.3.2), prázdné znaky (§6.3.4), komentáře (§6.3.3), tokeny (§6.4) a direktivy předběžného zpracování (§6.5). Z těchto základních prvků jsou v syntaktické gramatikě programu jazyka C# významné pouze tokeny (§6.2.4).
Lexikální zpracování kompilační jednotky jazyka C# se skládá z omezení souboru do posloupnosti tokenů, které se stanou vstupem syntaktické analýzy. Ukončovací znaky, prázdné znaky a komentáře mohou sloužit k oddělení tokenů a direktivy předběžného zpracování mohou způsobit vynechání oddílů kompilační jednotky, ale jinak tyto lexikální prvky nemají žádný vliv na syntaktickou strukturu programu jazyka C#.
Když několik lexikálních gramatických produkcí odpovídají sekvenci znaků v kompilační jednotce, lexikální zpracování vždy vytváří nejdelší možný lexikální prvek.
Příklad: Sekvence
//
znaků je zpracována jako začátek jednořádkového komentáře, protože lexikální prvek je delší než jeden/
token. konec příkladu
Některé tokeny jsou definovány sadou lexikálních pravidel; hlavní pravidlo a jedno nebo více dílčích pravidel. Ty druhé jsou v gramatice označeny fragment
, aby naznačily, že pravidlo definuje část jiného tokenu. Pravidla fragmentu nejsou považována za řazení lexikálních pravidel shora dolů.
Poznámka: V ANTLR
fragment
je klíčové slovo, které vytváří stejné chování definované zde. koncová poznámka
6.3.2 Ukončovací čáry
Ukončovací znaky rozdělují znaky kompilační jednotky jazyka C# na řádky.
New_Line
: New_Line_Character
| '\u000D\u000A' // carriage return, line feed
;
Kvůli kompatibilitě s nástroji pro úpravy zdrojového kódu, které přidávají značky koncového souboru a umožňují zobrazení kompilační jednotky jako posloupnost správně ukončených řádků, se pro každou kompilační jednotku v programu jazyka C# použijí následující transformace:
- Pokud je posledním znakem kompilační jednotky znak Control-Z (U+001A), odstraní se tento znak.
- Znak návratu na začátek řádku (U+000D) se přidá na konec kompilační jednotky, pokud tato jednotka kompilace není prázdná a pokud poslední znak kompilační jednotky není návrat na začátek řádku (U+000D), odřádkování (U+000A), znak dalšího řádku (U+0085), oddělovač řádků (U+2028) nebo oddělovač odstavců (U+2029).
Poznámka: Dodatečný návrat na začátek umožňuje programu ukončit PP_Directive (§6.5), který nemá koncovou New_Line. koncová poznámka
6.3.3 Komentáře
Podporují se dvě formy komentářů: komentáře s oddělovači a jednořádkové komentáře.
Komentář oddělený začíná znaky /*
a končí znaky */
. Komentáře s oddělovači můžou zabírat část řádku, jeden řádek nebo více řádků.
Příklad: Příklad
/* Hello, world program This program writes "hello, world" to the console */ class Hello { static void Main() { System.Console.WriteLine("hello, world"); } }
obsahuje oddělený komentář.
konec příkladu
Jednořádkový komentář začíná znaky //
a rozšiřuje se na konec řádku.
Příklad: Příklad
// Hello, world program // This program writes "hello, world" to the console // class Hello // any name will do for this class { static void Main() // this method must be named "Main" { System.Console.WriteLine("hello, world"); } }
zobrazuje několik jednořádkových komentářů.
konec příkladu
Comment
: Single_Line_Comment
| Delimited_Comment
;
fragment Single_Line_Comment
: '//' Input_Character*
;
fragment Input_Character
// anything but New_Line_Character
: ~('\u000D' | '\u000A' | '\u0085' | '\u2028' | '\u2029')
;
fragment New_Line_Character
: '\u000D' // carriage return
| '\u000A' // line feed
| '\u0085' // next line
| '\u2028' // line separator
| '\u2029' // paragraph separator
;
fragment Delimited_Comment
: '/*' Delimited_Comment_Section* ASTERISK+ '/'
;
fragment Delimited_Comment_Section
: SLASH
| ASTERISK* Not_Slash_Or_Asterisk
;
fragment Not_Slash_Or_Asterisk
: ~('/' | '*') // Any except SLASH or ASTERISK
;
Komentáře se nemohou vnořovat. Sekvence znaků /*
a */
nemá v rámci jednořádkových komentářů žádný speciální význam. To stejné platí se sekvencemi //
a /*
v komentářích s oddělovači.
Komentáře se nezpracují ve znakových a řetězcových literálech.
Poznámka: Tato pravidla musí být interpretována pečlivě. Například v následujícím příkladu komentář s oddělovači začíná před
A
a končí meziB
aC()
. Důvodem je, že// B */ C();
není ve skutečnosti jednořádkový komentář, protože
//
nemá žádný zvláštní význam v rámci odděleného komentáře, a tak*/
má jeho obvyklý zvláštní význam v daném řádku.Stejně tak ohraničený komentář, který začíná před
D
, končí předE
. Důvodem je, že"D */ "
ve skutečnosti není řetězcový literál, protože počáteční znak dvojité uvozovky se nachází uvnitř ohraničeného komentáře.Užitečným důsledkem
/*
a*/
bez zvláštního významu v rámci jednořádkového komentáře je, že blok řádků zdrojového kódu lze okomentovat tak, že umístíte//
na začátek každého řádku. Obecně platí, že nefunguje umístit/*
před tyto řádky a*/
za ně, protože to nesprávně zapouzdřuje ohraničené komentáře v bloku a obecně může zcela změnit strukturu těchto ohraničených komentářů.Příklad kódu:
static void Main() { /* A // B */ C(); Console.WriteLine(/* "D */ "E"); }
koncová poznámka
Single_Line_Commenty a Delimited_Commenty s konkrétními formáty mohou být použity jako dokumentační komentáře, jak je popsáno v §D.
6.3.4 Bílé místo
Prázdné znaky jsou definovány jako libovolný znak se třídou Unicode Zs (která obsahuje znak mezery) a také vodorovný znak tabulátoru, svislý znak tabulátoru a znak informačního kanálu formuláře.
Whitespace
: [\p{Zs}] // any character with Unicode class Zs
| '\u0009' // horizontal tab
| '\u000B' // vertical tab
| '\u000C' // form feed
;
6.4 Tokeny
6.4.1 Obecné
Existuje několik druhů tokenů: identifikátory, klíčová slova, literály, operátory a interpunkční znaky. Prázdné znaky a komentáře nejsou tokeny, ale fungují jako oddělovače tokenů.
token
: identifier
| keyword
| Integer_Literal
| Real_Literal
| Character_Literal
| String_Literal
| operator_or_punctuator
;
Poznámka: Toto je pravidlo analyzátoru ANTLR, nedefinuje lexikální token, ale spíše kolekci druhů tokenů. koncová poznámka
Únikové sekvence znaků Unicode 6.4.2
Unikódová escape sekvence představuje unikódový kódový bod. Řídicí sekvence Unicode se zpracovávají v identifikátorech (§6.4.3), literálech znaků (§6.4.5.5), běžných řetězcových literálech (§6.4.5.6) a interpolovaných běžných řetězcových výrazech (§12.8.3). Unikódová escape sekvence není zpracována v žádném jiném umístění (například pro vytvoření operátoru, interpunkčního znaménka nebo klíčového slova).
fragment Unicode_Escape_Sequence
: '\\u' Hex_Digit Hex_Digit Hex_Digit Hex_Digit
| '\\U' Hex_Digit Hex_Digit Hex_Digit Hex_Digit
Hex_Digit Hex_Digit Hex_Digit Hex_Digit
;
Escape sekvence znaku Unicode představuje jediný bod kódu Unicode, který je tvořen šestnáctkovým číslem následujícím za znaky \u nebo \U. Vzhledem k tomu, že jazyk C# používá 16bitové kódování bodů kódu Unicode ve znakových a řetězcových hodnotách, je bod kódu Unicode v rozsahu U+10000
U+10FFFF
reprezentován pomocí dvou náhradních jednotek kódu Unicode. Unicode kódy nad U+FFFF
nejsou povoleny ve znakových literálech. Výše uvedené U+10FFFF
body kódu Unicode jsou neplatné a nejsou podporovány.
Více překladů se neprovádí. Například řetězcový literál "\u005Cu005C"
je ekvivalentní "\u005C"
spíše než "\"
.
Poznámka: Hodnota
\u005C
Unicode je znak "\
". koncová poznámka
Příklad: Příklad
class Class1 { static void Test(bool \u0066) { char c = '\u0066'; if (\u0066) { System.Console.WriteLine(c.ToString()); } } }
zobrazuje několik použití
\u0066
, což je escape sekvence písmena "f
". Program je ekvivalentníclass Class1 { static void Test(bool f) { char c = 'f'; if (f) { System.Console.WriteLine(c.ToString()); } } }
konec příkladu
6.4.3 Identifikátory
Pravidla pro identifikátory uvedené v tomto dílčím seznamu přesně odpovídají těm, které doporučuje standardní příloha Unicode 15 s tím rozdílem, že podtržítko je povoleno jako počáteční znak (jak je tradiční v programovacím jazyce C), řídicí sekvence Unicode jsou povoleny v identifikátorech a znak "@
" je povolen jako předpona, která umožní použití klíčových slov jako identifikátorů.
identifier
: Simple_Identifier
| contextual_keyword
;
Simple_Identifier
: Available_Identifier
| Escaped_Identifier
;
fragment Available_Identifier
// excluding keywords or contextual keywords, see note below
: Basic_Identifier
;
fragment Escaped_Identifier
// Includes keywords and contextual keywords prefixed by '@'.
// See note below.
: '@' Basic_Identifier
;
fragment Basic_Identifier
: Identifier_Start_Character Identifier_Part_Character*
;
fragment Identifier_Start_Character
: Letter_Character
| Underscore_Character
;
fragment Underscore_Character
: '_' // underscore
| '\\u005' [fF] // Unicode_Escape_Sequence for underscore
| '\\U0000005' [fF] // Unicode_Escape_Sequence for underscore
;
fragment Identifier_Part_Character
: Letter_Character
| Decimal_Digit_Character
| Connecting_Character
| Combining_Character
| Formatting_Character
;
fragment Letter_Character
// Category Letter, all subcategories; category Number, subcategory letter.
: [\p{L}\p{Nl}]
// Only escapes for categories L & Nl allowed. See note below.
| Unicode_Escape_Sequence
;
fragment Combining_Character
// Category Mark, subcategories non-spacing and spacing combining.
: [\p{Mn}\p{Mc}]
// Only escapes for categories Mn & Mc allowed. See note below.
| Unicode_Escape_Sequence
;
fragment Decimal_Digit_Character
// Category Number, subcategory decimal digit.
: [\p{Nd}]
// Only escapes for category Nd allowed. See note below.
| Unicode_Escape_Sequence
;
fragment Connecting_Character
// Category Punctuation, subcategory connector.
: [\p{Pc}]
// Only escapes for category Pc allowed. See note below.
| Unicode_Escape_Sequence
;
fragment Formatting_Character
// Category Other, subcategory format.
: [\p{Cf}]
// Only escapes for category Cf allowed, see note below.
| Unicode_Escape_Sequence
;
Poznámka:
- Informace o výše uvedených třídách znaků Unicode naleznete v tématu Standard Unicode.
- Fragment Available_Identifier vyžaduje vyloučení klíčových slov a kontextových klíčových slov. Pokud se gramatika v této specifikaci zpracuje pomocí ANTLR, bude toto vyloučení automaticky zpracováno sémantikou ANTLR:
- Klíčová slova a kontextová klíčová slova se vyskytují v gramatikě jako literálové řetězce.
- ANTLR vytvoří implicitní pravidla lexikálních tokenů z těchto literálových řetězců.
- ANTLR považuje tato implicitní pravidla před explicitními lexikálními pravidly v gramatikě.
- Fragment Available_Identifier proto nebude odpovídat klíčovým slovům ani kontextovým klíčovým slovům, protože lexikální pravidla pro ně mají přednost.
- Fragment Escaped_Identifier zahrnuje upravená klíčová slova a kontextová klíčová slova, protože jsou součástí delšího tokenu počínajícího na
@
a při lexikálním zpracování vždy tvoří nejdelší možný lexikální prvek (§6.3.1).- Jak implementace vynucuje omezení povolených Unicode_Escape_Sequence hodnot je problém implementace.
koncová poznámka
Příklad: Příklady platných identifikátorů jsou
identifier1
,_identifier2
a@if
. konec příkladu
Identifikátor v vyhovujícím programu musí být v kanonickém formátu definovaném normalizačním formulářem Unicode C, jak je definováno standardní přílohou Unicode 15. Chování při výskytu identifikátoru, který není v normalizačním formuláři C, je definováno implementací; diagnostika však není povinná.
Předpona "@
" umožňuje použití klíčových slov jako identifikátorů, což je užitečné při propojení s jinými programovacími jazyky. Znak @
není ve skutečnosti součástí identifikátoru, takže identifikátor se může zobrazit v jiných jazycích jako normální identifikátor bez předpony. Identifikátor s předponou @
se nazývá doslovný identifikátor.
Poznámka: Použití
@
předpony pro identifikátory, které nejsou klíčovými slovy, je povoleno, ale důrazně nedoporučujeme v rámci stylu. koncová poznámka
Příklad: Příklad:
class @class { public static void @static(bool @bool) { if (@bool) { System.Console.WriteLine("true"); } else { System.Console.WriteLine("false"); } } } class Class1 { static void M() { cl\u0061ss.st\u0061tic(true); } }
definuje třídu s názvem "
class
" statickou metodou s názvem "static
", která přebírá parametr s názvem "bool
". Všimněte si, že protože Unicode sekvence nejsou povoleny v klíčových slovech, token "cl\u0061ss
" je identifikátor a je stejný jako identifikátor "@class
".konec příkladu
Dva identifikátory jsou považovány za stejné, pokud jsou stejné po použití následujících transformací v pořadí:
- Předpona "
@
", pokud je použita, je odebrána. - Každý Unicode_Escape_Sequence se transformuje na odpovídající znak Unicode.
- Odeberou se všechny Formatting_Character.
Sémantika pojmenovaného _
identifikátoru závisí na kontextu, ve kterém se zobrazuje:
- Může označit pojmenovaný prvek programu, například proměnnou, třídu nebo metodu nebo
- Může znamenat zahození (§ 9.2.9.2).
Identifikátory obsahující dva po sobě jdoucí podtržítka (U+005F
) jsou vyhrazeny pro použití implementací. Pokud je tento identifikátor definovaný, nevyžaduje se žádná diagnostika.
Poznámka: Například implementace může poskytovat rozšířená klíčová slova, která začínají dvěma podtržítky. koncová poznámka
6.4.4 Klíčová slova
Klíčové slovo je posloupnost znaků, která je vyhrazena a nelze ji použít jako identifikátor, s výjimkou případů, kdy je před ní znak @
.
keyword
: 'abstract' | 'as' | 'base' | 'bool' | 'break'
| 'byte' | 'case' | 'catch' | 'char' | 'checked'
| 'class' | 'const' | 'continue' | 'decimal' | DEFAULT
| 'delegate' | 'do' | 'double' | 'else' | 'enum'
| 'event' | 'explicit' | 'extern' | FALSE | 'finally'
| 'fixed' | 'float' | 'for' | 'foreach' | 'goto'
| 'if' | 'implicit' | 'in' | 'int' | 'interface'
| 'internal' | 'is' | 'lock' | 'long' | 'namespace'
| 'new' | NULL | 'object' | 'operator' | 'out'
| 'override' | 'params' | 'private' | 'protected' | 'public'
| 'readonly' | 'ref' | 'return' | 'sbyte' | 'sealed'
| 'short' | 'sizeof' | 'stackalloc' | 'static' | 'string'
| 'struct' | 'switch' | 'this' | 'throw' | TRUE
| 'try' | 'typeof' | 'uint' | 'ulong' | 'unchecked'
| 'unsafe' | 'ushort' | 'using' | 'virtual' | 'void'
| 'volatile' | 'while'
;
Kontextové klíčové slovo je sekvence znaků podobná identifikátoru, která má zvláštní význam v určitých kontextech, ale není rezervovaná, a lze ji použít jako identifikátor mimo tyto kontexty i tehdy, když je před ní znak @
.
contextual_keyword
: 'add' | 'alias' | 'ascending' | 'async' | 'await'
| 'by' | 'descending' | 'dynamic' | 'equals' | 'from'
| 'get' | 'global' | 'group' | 'into' | 'join'
| 'let' | 'nameof' | 'notnull' | 'on' | 'orderby'
| 'partial' | 'remove' | 'select' | 'set' | 'unmanaged'
| 'value' | 'var' | 'when' | 'where' | 'yield'
;
Poznámka: Klíčová slova a kontekstové klíčové slovo jsou pravidla analyzátoru, protože nezavádějí nové typy tokenů. Všechna klíčová slova a kontextová klíčová slova jsou definována implicitními lexikálními pravidly tak, jak se vyskytují jako literální řetězce v gramatikě (§6.2.3). koncová poznámka
Ve většině případů je syntaktické umístění kontextových klíčových slov takové, že je nelze zaměňovat s běžným použitím identifikátoru. Například v deklaraci vlastnosti get
mají identifikátory set
zvláštní význam (§15.7.3). Identifikátor jiný než get
nebo set
nikdy není v těchto umístěních povolen, takže toto použití není v konfliktu s použitím těchto slov jako identifikátorů.
V některých případech gramatika nestačí k rozlišení použití kontextového klíčového slova od identifikátorů. Ve všech takových případech bude stanoveno, jak mezi nimi rozlišit. Například kontextové klíčové slovo var
v implicitně zadaných deklarací místních proměnných (§13.6.2) může kolidovat s deklarovaným typem volaným var
, v takovém případě má deklarovaný název přednost před použitím identifikátoru jako kontextové klíčové slovo.
Dalším příkladem takové nejednoznačnosti je kontextové klíčové slovo await
(§12.9.8.1), které je považováno za klíčové slovo pouze v případě, že uvnitř metody deklarované async
, ale lze jej použít jako identifikátor jinde.
Stejně jako u klíčových slov lze kontextová klíčová slova použít jako běžné identifikátory přenástavbou znaku @
.
Poznámka: Při použití jako kontextová klíčová slova nemohou tyto identifikátory obsahovat Unicode_Escape_Sequences. koncová poznámka
6.4.5 Literály
6.4.5.1 Obecné
Literál (§12.8.2) je reprezentací hodnoty ve zdrojovém kódu.
literal
: boolean_literal
| Integer_Literal
| Real_Literal
| Character_Literal
| String_Literal
| null_literal
;
Poznámka: Literál je pravidlo analyzátoru, protože seskupuje jiné druhy tokenů a nezavádí nový druh tokenu. koncová poznámka
6.4.5.2 Logické literály
Existují dva Booleovské literály: true
a false
.
boolean_literal
: TRUE
| FALSE
;
Poznámka: boolean_literal je pravidlo analyzátoru, protože seskupuje jiné druhy tokenů a nezavádí nový druh tokenu. koncová poznámka
Typ boolean_literal je bool
.
6.4.5.3 Celočíselné literály
Celočíselné literály se používají k zápisu hodnot typů int
, uint
, long
, a ulong
. Celočíselné literály mají tři možné formy: desítkové, šestnáctkové a binární.
Integer_Literal
: Decimal_Integer_Literal
| Hexadecimal_Integer_Literal
| Binary_Integer_Literal
;
fragment Decimal_Integer_Literal
: Decimal_Digit Decorated_Decimal_Digit* Integer_Type_Suffix?
;
fragment Decorated_Decimal_Digit
: '_'* Decimal_Digit
;
fragment Decimal_Digit
: '0'..'9'
;
fragment Integer_Type_Suffix
: 'U' | 'u' | 'L' | 'l' |
'UL' | 'Ul' | 'uL' | 'ul' | 'LU' | 'Lu' | 'lU' | 'lu'
;
fragment Hexadecimal_Integer_Literal
: ('0x' | '0X') Decorated_Hex_Digit+ Integer_Type_Suffix?
;
fragment Decorated_Hex_Digit
: '_'* Hex_Digit
;
fragment Hex_Digit
: '0'..'9' | 'A'..'F' | 'a'..'f'
;
fragment Binary_Integer_Literal
: ('0b' | '0B') Decorated_Binary_Digit+ Integer_Type_Suffix?
;
fragment Decorated_Binary_Digit
: '_'* Binary_Digit
;
fragment Binary_Digit
: '0' | '1'
;
Typ celočíselného literálu je určen takto:
- Pokud literál nemá žádnou příponu, má první z těchto typů, ve kterých může být jeho hodnota reprezentována:
int
, ,uint
long
,ulong
. - Pokud je literál příponou
U
nebou
, má první z těchto typů, ve kterých může být jeho hodnota reprezentována:uint
,ulong
. - Pokud je literál příponou
L
nebol
, má první z těchto typů, ve kterých může být jeho hodnota reprezentována:long
,ulong
. - Pokud je literál opatřen příponou
UL
,Ul
,uL
,ul
,LU
,Lu
,lU
nebolu
, je typuulong
.
Pokud je hodnota reprezentovaná celočíselnou literálem mimo rozsah ulong
typu, dojde k chybě v době kompilace.
Poznámka: Ve stylu je doporučeno, aby
L
se při psaní literálů typul
"" použil místo "long
", protože je snadné zmást písmeno "l
" s číslicí "1
". koncová poznámka
Aby bylo možné zapisovat nejmenší možné int
hodnoty long
jako celočíselné literály, existují následující dvě pravidla:
-
Když Integer_Literal představující hodnotu
2147483648
(2³¹) a žádný Integer_Type_Suffix se nezobrazí jako token bezprostředně za tokenem unárního operátoru minus (§12.9.3), je výsledkem (obou tokenů) konstanta typu int s hodnotou−2147483648
(−2³¹). Ve všech ostatních situacích je takový Integer_Literal typuuint
. -
Pokud Integer_Literal představující hodnotu
9223372036854775808
(2⁶³) a žádný Integer_Type_Suffix nebo Integer_Type_SuffixL
nebol
se objeví jako token bezprostředně za tokenem unárního operátoru minus (§12.9.3), je výsledek (obou tokenů) konstantou typulong
s hodnotou−9223372036854775808
(−2⁶³). Ve všech ostatních situacích je takový Integer_Literal typuulong
.
Příklad:
123 // decimal, int 10_543_765Lu // decimal, ulong 1_2__3___4____5 // decimal, int _123 // not a numeric literal; identifier due to leading _ 123_ // invalid; no trailing _allowed 0xFf // hex, int 0X1b_a0_44_fEL // hex, long 0x1ade_3FE1_29AaUL // hex, ulong 0x_abc // hex, int _0x123 // not a numeric literal; identifier due to leading _ 0xabc_ // invalid; no trailing _ allowed 0b101 // binary, int 0B1001_1010u // binary, uint 0b1111_1111_0000UL // binary, ulong 0B__111 // binary, int __0B111 // not a numeric literal; identifier due to leading _ 0B111__ // invalid; no trailing _ allowed
konec příklad
6.4.5.4 Reálné literály
Skutečné literály se používají k zápisu hodnot typů float
, double
a decimal
.
Real_Literal
: Decimal_Digit Decorated_Decimal_Digit* '.'
Decimal_Digit Decorated_Decimal_Digit* Exponent_Part? Real_Type_Suffix?
| '.' Decimal_Digit Decorated_Decimal_Digit* Exponent_Part? Real_Type_Suffix?
| Decimal_Digit Decorated_Decimal_Digit* Exponent_Part Real_Type_Suffix?
| Decimal_Digit Decorated_Decimal_Digit* Real_Type_Suffix
;
fragment Exponent_Part
: ('e' | 'E') Sign? Decimal_Digit Decorated_Decimal_Digit*
;
fragment Sign
: '+' | '-'
;
fragment Real_Type_Suffix
: 'F' | 'f' | 'D' | 'd' | 'M' | 'm'
;
Pokud není zadán žádný Real_Type_Suffix , typ Real_Literal je double
.
V opačném případě Real_Type_Suffix určuje typ reálného literálu takto:
- Literál s příponou
F
nebof
je typufloat
.Příklad: Literály
1f
,1.5f
,1e10f
a123.456F
jsou všechny typufloat
. konec příkladu - Literál reálného čísla s příponou
D
nebod
je typudouble
.Příklad: Literály
1d
,1.5d
,1e10d
a123.456D
jsou všechny typudouble
. konec příkladu - Skutečný literál s příponou
M
nebom
je typudecimal
.Příklad: Literály
1m
,1.5m
,1e10m
a123.456M
jsou všechny typudecimal
. koncový příklad Tento literál se převede na hodnotudecimal
tak, že vezme přesnou hodnotu a v případě potřeby zaokrouhlí na nejbližší reprezentovanou hodnotu pomocí zaokrouhlení banky (§8.3.8). Jakékoli měřítko patrné v literálu se zachová, pokud hodnota není zaokrouhlená. Poznámka: Proto literál2.900m
bude analyzován tak, aby formovaldecimal
se znaménkem0
, koeficientem2900
a měřítkem3
. koncová poznámka
Pokud je velikost zadaného literálu příliš velká, aby byla reprezentována v zadaném typu, dojde k chybě v době kompilace.
Poznámka: Konkrétně Real_Literal nikdy nezpůsobí nekonečnou hodnotu v plovoucí řádové čárce. Nenulová Real_Literal však může být zaokrouhlená na nulu. koncová poznámka
Hodnota skutečného literálu typu float
nebo double
je určena pomocí režimu IEC 60559 „zaokrouhlení na nejbližší“ s vyrovnáním na „sudé“ (hodnota, kde je nejméně významný bit nulový) a všechny číslice jsou považovány za významné.
Poznámka: V reálném literálu jsou desetinná čísla vždy vyžadována za desetinnou čárkou. Například,
1.3F
je skutečný literál, ale1.F
není. koncová poznámkaPříklad:
1.234_567 // double .3e5f // float 2_345E-2_0 // double 15D // double 19.73M // decimal 1.F // parsed as a member access of F due to non-digit after . 1_.2F // invalid; no trailing _ allowed in integer part 1._234 // parsed as a member access of _234 due to non-digit after . 1.234_ // invalid; no trailing _ allowed in fraction .3e_5F // invalid; no leading _ allowed in exponent .3e5_F // invalid; no trailing _ allowed in exponent
konec příkladu
6.4.5.5 Znakové literály
Literál znaku představuje jeden znak a skládá se ze znaku v uvozovkách, jako například v 'a'
.
Character_Literal
: '\'' Character '\''
;
fragment Character
: Single_Character
| Simple_Escape_Sequence
| Hexadecimal_Escape_Sequence
| Unicode_Escape_Sequence
;
fragment Single_Character
// anything but ', \, and New_Line_Character
: ~['\\\u000D\u000A\u0085\u2028\u2029]
;
fragment Simple_Escape_Sequence
: '\\\'' | '\\"' | '\\\\' | '\\0' | '\\a' | '\\b' |
'\\f' | '\\n' | '\\r' | '\\t' | '\\v'
;
fragment Hexadecimal_Escape_Sequence
: '\\x' Hex_Digit Hex_Digit? Hex_Digit? Hex_Digit?
;
Poznámka: Znak, který následuje za znakem zpětného lomítka (
\
) v znaku , musí být jedním z následujících znaků:'
,"
,\
,0
,a
,b
,f
,n
,r
,t
,u
,U
,x
,v
. V opačném případě dojde k chybě kompilace. koncová poznámka
Poznámka: Použití
\x
Hexadecimal_Escape_Sequence produkce může být náchylné k chybám a obtížně čitelné z důvodu proměnlivého počtu šestnáctkových číslic za znakem\x
. Například v kódu:string good = "\x9Good text"; string bad = "\x9Bad text";
Mohlo by se zdát nejdříve, že počáteční znak je stejný (
U+0009
, znak tabulátoru) v obou řetězcích. Ve skutečnosti druhý řetězec začínáU+9BAD
, protože všechna tři písmena ve slově "Bad" jsou platné hexadecimální číslice. Jako otázka stylu se doporučuje se vyhnout\x
a použít buď specifické escape sekvence (\t
v tomto příkladu), nebo escape sekvenci s pevnou délkou\u
.koncová poznámka
Hexadecimální escape sekvence představuje jednu jednotku kódu Unicode UTF-16 s hodnotou vytvořenou hexadecimálním číslem za "\x
".
Pokud je hodnota reprezentovaná literálem znaku větší než U+FFFF
, dojde k chybě v době kompilace.
Unikódová úniková sekvence (§6.4.2) v literálu znaku musí být v rozmezí U+0000
do U+FFFF
.
Jednoduchá úniková sekvence představuje znak Unicode, jak je popsáno v tabulce níže.
Úniková sekvence | Název znaku | Bod kódu Unicode |
---|---|---|
\' |
Jednoduchá uvozovka | U+0027 |
\" |
Dvojitá uvozovka | U+0022 |
\\ |
Zpětné lomítko | U+005C |
\0 |
Null | U+0000 |
\a |
Výstrahy | U+0007 |
\b |
Klávesa Backspace | U+0008 |
\f |
Podavač formuláře | U+000C |
\n |
Nový řádek | U+000A |
\r |
Návrat vozíku | U+000D |
\t |
Horizontální tabulátor | U+0009 |
\v |
Vertikální tabulátor | U+000B |
Typ Character_Literal je char
.
6.4.5.6 Řetězcové literály
Jazyk C# podporuje dvě formy řetězcových literálů: běžné řetězcové literály a doslovné řetězcové literály. Běžný řetězcový literál se skládá z nula nebo více znaků uzavřených v dvojitých uvozovkách, jako například v "hello"
, a může obsahovat jak jednoduché escape sekvence (například \t
pro znak tabulátoru), tak šestnáctkové a Unicode escape sekvence.
Doslovný řetězcový literál se skládá ze znaku @
následovaného dvojitou uvozovkou, nuly nebo více znaků a koncovou dvojitou uvozovkou.
Příklad: Jednoduchý příklad je
@"hello"
. konec příkladu
Doslovný řetězcový literál interpretuje znaky mezi oddělovači doslovně, a jedinou výjimkou je Quote_Escape_Sequence, která představuje jeden znak s dvojitou uvozovkou. Zejména jednoduché escape sekvence, hexadecimální escape sekvence a Unicode escape sekvence nejsou zpracovány ve doslovných řetězcových literálech. Doslovný řetězcový literál může obsahovat více řádků.
String_Literal
: Regular_String_Literal
| Verbatim_String_Literal
;
fragment Regular_String_Literal
: '"' Regular_String_Literal_Character* '"'
;
fragment Regular_String_Literal_Character
: Single_Regular_String_Literal_Character
| Simple_Escape_Sequence
| Hexadecimal_Escape_Sequence
| Unicode_Escape_Sequence
;
fragment Single_Regular_String_Literal_Character
// anything but ", \, and New_Line_Character
: ~["\\\u000D\u000A\u0085\u2028\u2029]
;
fragment Verbatim_String_Literal
: '@"' Verbatim_String_Literal_Character* '"'
;
fragment Verbatim_String_Literal_Character
: Single_Verbatim_String_Literal_Character
| Quote_Escape_Sequence
;
fragment Single_Verbatim_String_Literal_Character
: ~["] // anything but quotation mark (U+0022)
;
fragment Quote_Escape_Sequence
: '""'
;
Příklad: Příklad
string a = "Happy birthday, Joel"; // Happy birthday, Joel string b = @"Happy birthday, Joel"; // Happy birthday, Joel string c = "hello \t world"; // hello world string d = @"hello \t world"; // hello \t world string e = "Joe said \"Hello\" to me"; // Joe said "Hello" to me string f = @"Joe said ""Hello"" to me"; // Joe said "Hello" to me string g = "\\\\server\\share\\file.txt"; // \\server\share\file.txt string h = @"\\server\share\file.txt"; // \\server\share\file.txt string i = "one\r\ntwo\r\nthree"; string j = @"one two three";
zobrazuje různé řetězcové literály. Poslední řetězcový literál ,
j
je doslovný řetězcový literál, který zahrnuje více řádků. Znaky mezi uvozovkami, včetně prázdných znaků, jako jsou znaky nového řádku, se zachovají doslovně a každý pár dvou uvozovek se nahradí jedním takovým znakem.konec příkladu
Poznámka: Všechny konce řádků v doslovných řetězcových literálech jsou součástí výsledného řetězce. Pokud jsou přesné znaky používané k vytvoření konců řádků sémanticky relevantní pro aplikaci, všechny nástroje, které překládají konce řádků ve zdrojovém kódu do různých formátů (například mezi "
\n
" a "\r\n
"), změní chování aplikace. Vývojáři by v takových situacích měli být opatrní. koncová poznámka
Poznámka: Protože šestnáctková řídicí sekvence může mít proměnlivý počet šestnáctkových číslic, řetězcový literál
"\x123"
obsahuje jeden znak s šestnáctkovou hodnotou123
. Chcete-li vytvořit řetězec obsahující znak s šestnáctkovou hodnotou12
následovanou znakem3
, mohl by jeden napsat"\x00123"
nebo"\x12"
+"3"
místo toho. koncová poznámka
Typ String_Literal je string
.
Každý řetězcový literál nemusí nezbytně vytvářet novou instanci řetězce. Pokud se dva nebo více řetězcových literálů, které jsou ekvivalentní podle operátoru rovnosti řetězce (§12.12.8), objeví ve stejném sestavení, tyto řetězcové literály odkazují na stejnou instanci řetězce.
Příklad: Například výstup vytvořený pomocí
class Test { static void Main() { object a = "hello"; object b = "hello"; System.Console.WriteLine(a == b); } }
je
True
to proto, že dva literály odkazují na stejnou instanci řetězce.konec příkladu
6.4.5.7 Literál null
null_literal
: NULL
;
Poznámka: null_literal je pravidlo analyzátoru, protože nezavádí nový druh tokenu. koncová poznámka
A null_literal představuje hodnotu null
. Typ nemá, ale lze jej převést na jakýkoli typ odkazu nebo typ hodnoty null prostřednictvím literálového převodu null (§10.2.7).
6.4.6 Operátory a interpunkční znaky
Existuje několik druhů operátorů a interpunkčních znamétek. Operátory se používají ve výrazech k popisu operací zahrnujících jeden nebo více operandů.
Příklad: Výraz
a + b
používá+
operátor k přidání dvou operandůa
ab
. konec příkladu
Interpunkční znaky slouží k seskupení a oddělení.
operator_or_punctuator
: '{' | '}' | '[' | ']' | '(' | ')' | '.' | ',' | ':' | ';'
| '+' | '-' | ASTERISK | SLASH | '%' | '&' | '|' | '^' | '!' | '~'
| '=' | '<' | '>' | '?' | '??' | '::' | '++' | '--' | '&&' | '||'
| '->' | '==' | '!=' | '<=' | '>=' | '+=' | '-=' | '*=' | '/=' | '%='
| '&=' | '|=' | '^=' | '<<' | '<<=' | '=>' | '??='
;
right_shift
: '>' '>'
;
right_shift_assignment
: '>' '>='
;
Poznámka: right_shift a right_shift_assignment jsou pravidla analyzátoru, protože nezavádějí nový druh tokenu, ale představují sekvenci dvou tokenů. Pravidlo operator_or_punctuator existuje pouze pro popisné účely a nepoužívá se jinde v gramatikě. koncová poznámka
right_shift se skládá ze dvou tokenů >
a >
.
Podobně se right_shift_assignment skládá ze dvou tokenů >
a >=
. Na rozdíl od jiných produkcí v syntaktické gramatice nejsou mezi těmito dvěma tokeny v každé z těchto produkcí povoleny žádné znaky žádného druhu (ani prázdné mezery). Tyto výroby jsou zpracovávány speciálně za účelem zajištění správného zpracování type_parameter_lists (§15.2.3).
Poznámka: Před přidáním obecných typů do jazyka C# byly
>>
a>>=
oba samostatné tokeny. Syntaxe obecných typů ale používá<
,>
znaky k ohraničení parametrů typu a argumentů typu. Často je žádoucí používat vnořené konstruované typy, napříkladList<Dictionary<string, int>>
. Místo toho, aby programátor oddělil>
a>
mezerou, byla změněna definice dvou operátorů_nebo_interpunkčních_znaků. koncová poznámka
6.5 Direktivy předběžného zpracování
6.5.1 Obecné
Direktivy předběžného zpracování poskytují možnost podmíněně přeskočit části jednotek kompilace, hlásit chybové a upozorňující podmínky, vypsat jedinečné oblasti zdrojového kódu a nastavit kontext s možnou hodnotou null.
Poznámka: Výraz "direktivy předběžného zpracování" se používá pouze pro konzistenci s programovacími jazyky C a C++. V jazyce C# neexistuje žádný samostatný krok předběžného zpracování; Direktivy předběžného zpracování se zpracovávají jako součást lexikální fáze analýzy. koncová poznámka
PP_Directive
: PP_Start PP_Kind PP_New_Line
;
fragment PP_Kind
: PP_Declaration
| PP_Conditional
| PP_Line
| PP_Diagnostic
| PP_Region
| PP_Pragma
| PP_Nullable
;
// Only recognised at the beginning of a line
fragment PP_Start
// See note below.
: { getCharPositionInLine() == 0 }? PP_Whitespace? '#' PP_Whitespace?
;
fragment PP_Whitespace
: ( [\p{Zs}] // any character with Unicode class Zs
| '\u0009' // horizontal tab
| '\u000B' // vertical tab
| '\u000C' // form feed
)+
;
fragment PP_New_Line
: PP_Whitespace? Single_Line_Comment? New_Line
;
Poznámka:
- Gramatika preprocesoru definuje jeden lexikální token
PP_Directive
používaný pro všechny direktivy předběžného zpracování. Sémantika každé direktivy předzpracování jsou definována v této specifikaci jazyka, ale ne v tom, jak je implementovat.- Fragment
PP_Start
musí být rozpoznáván pouze na začátku přímky,getCharPositionInLine() == 0
lexikální predikát ANTLR výše naznačuje jeden způsob, jak toho dosáhnout a je informativní pouze, implementace může použít jinou strategii.koncová poznámka
K dispozici jsou následující direktivy předběžného zpracování:
-
#define
a#undef
, které se používají k definování a ne-definování symbolů podmíněné kompilace (§6.5.4). -
#if
,#elif
,#else
a#endif
, které se používají ke vynechání podmíněných oddílů zdrojového kódu (§6.5.5). -
#line
, který slouží k řízení čísel řádků emitovaných pro chyby a upozornění (§6.5.8). -
#error
, který se používá k hlášení chyb (§6.5.6). -
#region
a#endregion
, které se používají k explicitní označení částí zdrojového kódu (§6.5.7). -
#nullable
, který se používá k určení kontextu s možnou hodnotou null (§6.5.9). -
#pragma
, který slouží k určení volitelných kontextových informací kompilátoru (§6.5.10).
Direktiva předběžného zpracování vždy zabírá samostatný řádek zdrojového kódu a vždy začíná znakem #
a názvem direktivy předběžného zpracování. Před znakem #
a mezi znakem #
a názvem direktivy může dojít k prázdnému znaku.
Zdrojový řádek obsahující direktivu #define
, #undef
, #if
, #elif
, #else
, #endif
, #line
, #endregion
nebo #nullable
může končit jedním řádkovým komentářem. Komentáře s oddělovači ( /* */
styl komentářů) nejsou povoleny na zdrojových řádcích obsahujících direktivy předběžného zpracování.
Direktivy předběžného zpracování nejsou součástí syntaktické gramatiky jazyka C#. Direktivy předběžného zpracování však lze použít k zahrnutí nebo vyloučení sekvencí tokenů a mohou tímto způsobem ovlivnit význam programu jazyka C#.
Příklad: Při kompilaci programu
#define A #undef B class C { #if A void F() {} #else void G() {} #endif #if B void H() {} #else void I() {} #endif }
výsledkem je stejná posloupnost tokenů jako program.
class C { void F() {} void I() {} }
Proto, zatímco lexiky jsou tyto dva programy poměrně odlišné, syntakticky, jsou identické.
konec příkladu
6.5.2 Symboly podmíněné kompilace
Funkce podmíněné kompilace poskytované direktivami #if
, #elif
, #else
a #endif
se řídí výrazy předběžného zpracování (§6.5.3) a symboly podmíněné kompilace.
fragment PP_Conditional_Symbol
// Must not be equal to tokens TRUE or FALSE. See note below.
: Basic_Identifier
;
Poznámka: Jak implementace vynucuje omezení povolené Basic_Identifier hodnoty je problém implementace. koncová poznámka
Dva symboly podmíněné kompilace jsou považovány za stejné, pokud jsou stejné po použití následujících transformací v pořadí:
- Každý Unicode_Escape_Sequence se transformuje na odpovídající znak Unicode.
- Odeberou se všechny Formatting_Characters .
Symbol podmíněné kompilace má dva možné stavy: definované nebo nedefinované. Na začátku lexikálního zpracování kompilační jednotky není definován symbol podmíněné kompilace, pokud ho explicitně nedefinuje externí mechanismus (například možnost kompilátoru příkazového řádku).
#define
Při zpracování direktivy se v této jednotce kompilace definuje symbol podmíněné kompilace pojmenovaný v této direktivě. Symbol zůstane definován, dokud #undef
se nezpracuje direktiva pro stejný symbol nebo dokud se nedosáhne konce kompilační jednotky. Implikací je, že #define
direktivy #undef
v jedné jednotce kompilace nemají žádný vliv na jiné kompilační jednotky ve stejném programu.
Při odkazu ve výrazu předběžného zpracování (§6.5.3) má definovaný symbol podmíněné kompilace logickou hodnotu true
a nedefinovaný symbol podmíněné kompilace má logickou hodnotu false
. Před odkazem na symboly předběžného zpracování není nutné explicitně deklarovat symboly podmíněné kompilace. Místo toho jsou nedefinované symboly jednoduše nedefinované a mají tedy hodnotu false
.
Obor názvů pro symboly podmíněné kompilace je odlišný a oddělený od všech ostatních pojmenovaných entit v programu jazyka C#. Na symboly podmíněné kompilace lze odkazovat pouze ve #define
direktivách a #undef
ve výrazech před zpracováním.
6.5.3 Výrazy předběžného zpracování
Výrazy pro předzpracování mohou nastat v #if
a #elif
direktivách.
!
Operátory (pouze předpona logická negace), ==
, !=
, &&
a ||
jsou povoleny v předzpracovacích výrazech a závorky lze použít pro seskupení.
fragment PP_Expression
: PP_Whitespace? PP_Or_Expression PP_Whitespace?
;
fragment PP_Or_Expression
: PP_And_Expression (PP_Whitespace? '||' PP_Whitespace? PP_And_Expression)*
;
fragment PP_And_Expression
: PP_Equality_Expression (PP_Whitespace? '&&' PP_Whitespace?
PP_Equality_Expression)*
;
fragment PP_Equality_Expression
: PP_Unary_Expression (PP_Whitespace? ('==' | '!=') PP_Whitespace?
PP_Unary_Expression)*
;
fragment PP_Unary_Expression
: PP_Primary_Expression
| '!' PP_Whitespace? PP_Unary_Expression
;
fragment PP_Primary_Expression
: TRUE
| FALSE
| PP_Conditional_Symbol
| '(' PP_Whitespace? PP_Expression PP_Whitespace? ')'
;
Při odkazu v předzpracovatelském výrazu má definovaný symbol podmíněné kompilace logickou hodnotu true
a nedefinovaný symbol podmíněné kompilace má logickou hodnotu false
.
Vyhodnocení výrazu předběžného zpracování vždy vrátí logickou hodnotu. Pravidla vyhodnocení výrazu předběžného zpracování jsou stejná jako pravidla pro konstantní výraz (§12.23), s tím rozdílem, že jedinými uživatelem definovanými entitami, na které lze odkazovat, jsou symboly podmíněné kompilace.
6.5.4 Definiční direktivy
Direktivy definice slouží k definování nebo nedefinování symbolů podmíněné kompilace.
fragment PP_Declaration
: 'define' PP_Whitespace PP_Conditional_Symbol
| 'undef' PP_Whitespace PP_Conditional_Symbol
;
Zpracování #define
direktivy způsobí, že se daný symbol podmíněné kompilace definuje, počínaje zdrojovým řádkem, který následuje za direktivou. Stejně tak zpracování #undef
direktivy způsobí, že se daný symbol podmíněné kompilace stane nedefinovaným, počínaje zdrojovým řádkem, který následuje za direktivou.
Všechny #define
a #undef
direktivy v kompilační jednotce musí být umístěny před prvním tokenem (§6.4) v kompilační jednotce; jinak dojde k chybě během doby kompilace. Intuitivně #define
a #undef
direktivy musí předcházet jakémukoli "skutečnému kódu" v kompilační jednotce.
Příklad: Příklad:
#define Enterprise #if Professional || Enterprise #define Advanced #endif namespace Megacorp.Data { #if Advanced class PivotTable {...} #endif }
je platný, protože
#define
direktivy předcházejí prvnímu tokenu (namespace
klíčovému slovu) v jednotce kompilace.konec příkladu
Příklad: Následující příklad má za následek chybu v době kompilace, protože #define se řídí skutečným kódem:
#define A namespace N { #define B #if B class Class1 {} #endif }
konec příkladu
Symbol #define
může definovat symbol podmíněné kompilace, který je již definován, aniž by mezi tím byl jakýkoli zásah #undef
pro tento symbol.
Příklad: Následující příklad definuje symbol podmíněné kompilace A a pak ho znovu definuje.
#define A #define A
U kompilátorů, které umožňují definovat symboly podmíněné kompilace jako možnosti kompilace, je alternativním způsobem, jak takové předefinování nastat, definovat symbol jako možnost kompilátoru i ve zdroji.
konec příkladu
Může #undef
"zrušit platnost definice" symbolu podmíněné kompilace, který není definován.
Příklad: Následující příklad definuje symbol
A
podmíněné kompilace a pak ho dvakrát nedefinuje, i když druhý#undef
nemá žádný vliv, je stále platný.#define A #undef A #undef A
konec příkladu
6.5.5 Direktivy podmíněné kompilace
Direktivy podmíněné kompilace slouží k podmíněnému zahrnutí nebo vyloučení částí kompilační jednotky.
fragment PP_Conditional
: PP_If_Section
| PP_Elif_Section
| PP_Else_Section
| PP_Endif
;
fragment PP_If_Section
: 'if' PP_Whitespace PP_Expression
;
fragment PP_Elif_Section
: 'elif' PP_Whitespace PP_Expression
;
fragment PP_Else_Section
: 'else'
;
fragment PP_Endif
: 'endif'
;
Direktivy podmíněné kompilace se zapisují ve skupinách skládajících se z #if
direktivy, nuly nebo více #elif
direktiv, nuly nebo jedné #else
direktivy a #endif
direktivy. Mezi direktivami jsou podmíněné oddíly zdrojového kódu. Každá část je řízena bezprostředně předcházející direktivou. Podmíněný oddíl může sám obsahovat vnořené direktivy podmíněné kompilace za předpokladu, že tyto direktivy tvoří kompletní skupiny.
Pro normální lexikální zpracování je vybrána maximálně jedna z obsažených podmíněných oddílů:
-
PP_Expressiony direktiv
#if
a#elif
se vyhodnocují ve stanoveném pořadí, dokud jedna není rovnatrue
. Pokud výraz vede ktrue
, je vybrán podmíněný oddíl následující po odpovídající direktivě. - Pokud všechny PP_Expressiony vyprodukují
false
, a pokud je přítomna směrnice#else
, je vybrána podmínková sekce následující po směrnici#else
. - Jinak není vybraný žádný podmíněný oddíl.
Vybraný podmíněný oddíl, pokud existuje, je zpracován jako normální input_section: zdrojový kód obsažený v oddílu se řídí lexikální gramatikou; tokeny jsou generovány ze zdrojového kódu v oddílu; a direktivy předběžného zpracování v oddílu mají předepsané účinky.
Všechny zbývající podmíněné oddíly se přeskočí a ze zdrojového kódu se negenerují žádné tokeny s výjimkou direktiv před zpracováním. Proto může být vynechaný zdrojový kód, s výjimkou direktiv předzpracování, lexikálně nesprávný. Přeskočené direktivy předběžného zpracování jsou lexicky správné, ale jinak se nezpracují. Všechny vnořené podmíněné oddíly (vnořené v #if...#endif
konstruktech) se také přeskočí, pokud jsou v rámci podmíněného oddílu, který je přeskočen.
Poznámka: Výše uvedená gramatika nezachycuje povolení, že podmíněné oddíly mezi direktivami předběžného zpracování mohou být špatně formátovány lexicky. Gramatika proto není připravená na ANTLR, protože podporuje pouze lexicky správný vstup. koncová poznámka
Příklad: Následující příklad ukazuje, jak mohou být direktivy podmíněné kompilace vnořeny:
#define Debug // Debugging on #undef Trace // Tracing off class PurchaseTransaction { void Commit() { #if Debug CheckConsistency(); #if Trace WriteToLog(this.ToString()); #endif #endif CommitHelper(); } ... }
S výjimkou direktiv předběžného zpracování nepodléhá přeskočený zdrojový kód lexikální analýze. Toto je například platné navzdory neukončenému komentáři v oddílu
#else
:#define Debug // Debugging on class PurchaseTransaction { void Commit() { #if Debug CheckConsistency(); #else /* Do something else #endif } ... }
Všimněte si však, že direktivy předběžného zpracování musí být lexicky správné i v přeskočených oddílech zdrojového kódu.
Direktivy předběžného zpracování se nezpracují, když se zobrazí uvnitř víceřádkových vstupních prvků. Například program:
class Hello { static void Main() { System.Console.WriteLine(@"hello, #if Debug world #else Nebraska #endif "); } }
výsledky ve výstupu:
hello, #if Debug world #else Nebraska #endif
Ve zvláštních případech může sada direktiv před zpracováním, které se zpracovávají, záviset na vyhodnocení pp_expression. Příklad:
#if X /* #else /* */ class Q { } #endif
vždy vytvoří stejný tokenový proud (
class
Q
{
}
), bez ohledu na to, zda jeX
definován nebo ne. PokudX
je definována, jediné zpracované direktivy jsou#if
a#endif
vzhledem k víceřádkovému komentáři. PokudX
není definována, jsou tři direktivy (#if
,#else
,#endif
) součástí sady direktiv.konec příkladu
6.5.6 Diagnostické direktivy
Diagnostické direktivy slouží ke generování explicitně chybových a upozorňujících zpráv, které jsou hlášeny stejným způsobem jako jiné chyby a upozornění v době kompilace.
fragment PP_Diagnostic
: 'error' PP_Message?
| 'warning' PP_Message?
;
fragment PP_Message
: PP_Whitespace Input_Character*
;
Příklad: Příklad
#if Debug && Retail #error A build can't be both debug and retail #endif class Test {...}
vyvolá chybu při kompilaci („Sestavení nemůže být zároveň laděné i finální“), pokud jsou definovány podmíněné kompilace symboly
Debug
aRetail
. Všimněte si, že PP_Message může obsahovat libovolný text; konkrétně nemusí obsahovat správně formátované tokeny, jak je znázorněno jediným uvozovkou ve slověcan't
.konec příkladu
6.5.7 Direktivy oblastí
Direktivy oblasti se používají k označení explicitních oblastí zdrojového kódu.
fragment PP_Region
: PP_Start_Region
| PP_End_Region
;
fragment PP_Start_Region
: 'region' PP_Message?
;
fragment PP_End_Region
: 'endregion' PP_Message?
;
K oblasti není připojen žádný sémantický význam; oblasti jsou určeny pro použití programátorem nebo automatizovanými nástroji k označení části zdrojového kódu. Musí existovat jedna #endregion
směrnice odpovídající každé #region
směrnici. Zpráva zadaná v #region
nebo #endregion
direktivě podobně nemá žádný sémantický význam; slouží pouze k identifikaci oblasti. Shodné direktivy #region
a #endregion
mohou mít různé PP_Message.
Lexikální zpracování oblasti:
#region
...
#endregion
odpovídá přesně lexikálnímu zpracování direktivy podmíněné kompilace ve formě:
#if true
...
#endif
Poznámka: To znamená, že oblast může obsahovat jednu nebo více
#if
/.../#endif
, či může být obsažena s podmínkovým blokem v rámci#if
/.../#endif
; ale oblast se nemůže překrývat jen částí#if
/.../#endif
, nebo začít a končit v různých podmínkových blocích. koncová poznámka
6.5.8 Direktivy řádků
Direktivy řádků lze použít ke změně čísel řádků a názvů jednotek kompilace hlášených kompilátorem ve výstupu, jako jsou upozornění a chyby. Tyto hodnoty se používají také atributy informací o volajícím (§22.5.6).
Poznámka: Direktivy řádků se nejčastěji používají v nástrojích pro metaprogramování, které generují zdrojový kód jazyka C# z jiného textového vstupu. koncová poznámka
fragment PP_Line
: 'line' PP_Whitespace PP_Line_Indicator
;
fragment PP_Line_Indicator
: Decimal_Digit+ PP_Whitespace PP_Compilation_Unit_Name
| Decimal_Digit+
| DEFAULT
| 'hidden'
;
fragment PP_Compilation_Unit_Name
: '"' PP_Compilation_Unit_Name_Character* '"'
;
fragment PP_Compilation_Unit_Name_Character
// Any Input_Character except "
: ~('\u000D' | '\u000A' | '\u0085' | '\u2028' | '\u2029' | '"')
;
Pokud nejsou k dispozici žádné direktivy #line
, kompilátor hlásí ve výstupu čísla řádků a názvy jednotek kompilace. Při zpracování #line
direktivy, která obsahuje PP_Line_Indicator, který není default
, zachází kompilátor s řádkem za direktivou tak, že má dané číslo řádku (a název kompilační jednotky, pokud je zadán).
Maximální povolená hodnota Decimal_Digit+
je definována implementací.
Direktiva #line default
vrátí účinek všech předchozích #line
direktiv. Kompilátor hlásí skutečné informace o řádcích pro následující řádky přesně tak, jako kdyby nebyly zpracovány žádné direktivy #line
.
Směrnice #line hidden
nemá žádný vliv na jednotku kompilace ani na čísla řádků hlášená v chybových zprávách nebo vytvořená použitím CallerLineNumberAttribute
(§22.5.6.2). Má mít vliv na ladicí nástroje pracující na zdrojové úrovni tak, aby při ladění všechny řádky mezi direktivou #line hidden
a následnou direktivou #line
(s výjimkou #line hidden
) neměly žádné informace o čísle řádku a byly zcela přeskočeny při procházení kódem.
Poznámka: I když PP_Compilation_Unit_Name může obsahovat text, který vypadá jako řídicí sekvence, takový text není řídicí sekvencí. V tomto kontextu znak '
\
' jednoduše označí běžný znak zpětného lomítka. koncová poznámka
6.5.9 Nulovatelná direktiva
Direktiva s možnou hodnotou null řídí kontext s možnou hodnotou null, jak je popsáno níže.
fragment PP_Nullable
: 'nullable' PP_Whitespace PP_Nullable_Action
(PP_Whitespace PP_Nullable_Target)?
;
fragment PP_Nullable_Action
: 'disable'
| 'enable'
| 'restore'
;
fragment PP_Nullable_Target
: 'warnings'
| 'annotations'
;
Direktiva pro nulovatelnost nastaví dostupné příznaky pro následující řádky kódu, a to až do chvíle, kdy ji nepřepíše jiná direktiva pro nulovatelnost nebo dokud nedosáhne konce kompilační jednotky. Kontext s možnou hodnotou null obsahuje dva příznaky: poznámky a upozornění. Účinek každé formy nullable direktivy je následující:
-
#nullable disable
: Zakáže anotace s hodnotou null i upozornění s hodnotou null. -
#nullable enable
: Povolí příznaky upozornění s možnou hodnotou null i poznámek s možnou hodnotou null. -
#nullable restore
: Obnoví poznámky i příznaky upozornění do stavu určeného externím mechanismem (pokud existuje). -
#nullable disable annotations
: Zakáže příznak poznámek s možnou hodnotou null. Příznak upozornění s možnou hodnotou null nemá vliv. -
#nullable enable annotations
: Povolí příznak poznámek s možnou hodnotou null. Příznak upozornění s možnou hodnotou null nemá vliv. -
#nullable restore annotations
: Obnoví příznak nulovatelných anotací do stavu určeného externím mechanismem, pokud je nějaký. Příznak upozornění s možnou hodnotou null nemá vliv. -
#nullable disable warnings
: Zakáže příznak upozornění s možnou hodnotou null. Příznak poznámek s možnou hodnotou null nemá vliv. -
#nullable enable warnings
: Povolí příznak upozornění s možnou hodnotou null. Příznak poznámek s možnou hodnotou null nemá vliv. -
#nullable restore warnings
: Obnoví příznak upozornění s možnou hodnotou null do stavu určeného externím mechanismem, pokud existuje. Příznak poznámek s možnou hodnotou null nemá vliv.
Stav nullovatelnosti výrazů je sledován neustále. Stav příznaku anotace a přítomnost nebo absence poznámky s možnou hodnotou null určuje ?
počáteční stav null deklarace proměnné. Upozornění se vydávají pouze v případech, kdy je příznak upozornění povolený.
Příklad: Příklad
#nullable disable string x = null; string y = ""; #nullable enable Console.WriteLine(x.Length); // Warning Console.WriteLine(y.Length);
vytvoří upozornění v době kompilace ("
x
jenull
"). Stav s možnoux
hodnotou null je sledován všude. Při povolení příznaku upozornění se zobrazí upozornění.konec příkladu
6.5.10 Direktivy Pragma
Direktiva #pragma
předběžného zpracování slouží k určení kontextových informací kompilátoru.
Poznámka: Kompilátor může například poskytnout
#pragma
direktivy, které
- Povolte nebo zakažte konkrétní zprávy upozornění při kompilaci dalšího kódu.
- Určete, které optimalizace se mají použít pro následující kód.
- Zadejte informace, které má ladicí program používat.
koncová poznámka
fragment PP_Pragma
: 'pragma' PP_Pragma_Text?
;
fragment PP_Pragma_Text
: PP_Whitespace Input_Character*
;
Input_Characterv PP_Pragma_Text interpretuje kompilátor způsobem definovaným implementací. Informace uvedené ve #pragma
směrnici nemění sémantiku programu. Směrnice #pragma
mění pouze chování kompilátoru, které je mimo rozsah této specifikace jazyka. Pokud kompilátor nemůže interpretovat Input_Characters, může kompilátor vytvořit upozornění; nesmí však vést k chybě v době kompilace.
Poznámka: PP_Pragma_Text může obsahovat libovolný text; konkrétně nemusí obsahovat správně formátované tokeny. koncová poznámka
ECMA C# draft specification