Sdílet prostřednictvím


6 Lexikální struktura

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ů:

  1. Transformace, která převádí soubor z určitého znakového schématu a schématu kódování na posloupnost znaků Unicode.
  2. Lexikální analýza, která překládá datový proud vstupních znaků Unicode do datového proudu tokenů.
  3. 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 oddělující řádek (nebo sekvenci) v jiné znakové sadě na dvouznakovou sekvenci skládající se ze znaku U+000D (Unicode) a znaku u+000A. Ve většině případů tato transformace nebude mít žádné viditelné účinky; bude však 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 ve formě Extended Backus-Naur nástroje ANTLR.

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. Při této kvalifikaci se tato specifikace pokusí minimalizovat mezeru mezi zadanou gramatikou a požadovanou k vytvoření lexeru a analyzátoru v ANTLR.

ANTLR rozlišuje mezi lexikálním a syntaktickým analyzátorem anTLR, gramatikami ve svém zápisu tím, že začíná lexikálními pravidly velkým písmenem a analyzátorem pravidel s 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, dílčích jazycích a přílohách, které následují za tímto podklíčem. 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í do formulářů programů jazyka C#.

Každá kompilační jednotka v programu jazyka C# musí odpovídat compilation_unit produkce (§14.2) syntaktické gramatiky.

6.2.5 Gramatické nejednoznačnosti

Produkce pro simple_name (§12.8.4) a member_access (§12.8.7) mohou vést k nejednoznačnostem gramatiky pro výrazy.

Příklad: Příkaz:

F(G<A, B>(7));

lze interpretovat jako volání F se dvěma argumenty G < A a B > (7). Alternativně je možné ji interpretovat jako volání F s jedním argumentem, což je volání obecné metody G se dvěma argumenty typu a jedním pravidelným argumentem.

end example

Je-li možné analyzovat posloupnost tokenů (v kontextu) jako simple_name (§12.8.4), member_access (§12.8.7) nebo pointer_member_access (§23.6.3) končící na type_argument_list (§8.4.2), je token bezprostředně po závěrečném tokenu prozkoumán, aby se zjistilo, zda je>

  • jeden z ( ) ] } : ; , . ? == != | ^ && || & [; nebo
  • Jeden z relačních operátorů < <= >= is as; nebo
  • Kontextové klíčové slovo dotazu zobrazené uvnitř výrazu dotazu; nebo
  • V určitých kontextech se identifikátor považuje za nejednoznačný token. Tyto kontexty jsou tam, kde posloupnost tokenů, které jsou nejednoznačné, bezprostředně předchází některému z klíčových slov is, case nebo outdojde při analýze prvního prvku literálu řazené kolekce členů (v takovém případě jsou tokeny předcházející ( nebo : a identifikátor následuje ,) nebo další prvek literálu řazené kolekce členů.

Pokud je mezi tímto seznamem nebo identifikátorem v tomto kontextu následující token, type_argument_list se zachovají jako součást simple_name, member_access nebo pointer_member-access a všechny další možné analýzy posloupnosti tokenů se zahodí. V opačném případě se type_argument_list nepovažuje za součást simple_name, member_access nebo pointer_member_access, i když neexistuje žádná další možná analýza posloupnosti tokenů.

Poznámka: Tato pravidla se nepoužijí při analýze type_argument_list v namespace_or_type_name (§7.8). koncová poznámka

Příklad: Příkaz:

F(G<A, B>(7));

bude podle tohoto pravidla interpretována jako volání F s jedním argumentem, což je volání obecné metody G se dvěma argumenty typu a jedním pravidelným argumentem. Příkazy

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. Příkaz

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 příkazu

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 řazená kolekce členů se dvěma prvky, z nichž každý porovnává.

Výraz (A<B,C> D, E) je řazená kolekce členů se dvěma prvky, z nichž první je výraz deklarace.

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í je out deklarace.

e is A<B> C Výraz používá vzor deklarace.

Popisek case A<B> C: případu používá vzor deklarace.

end example

Při uznání relational_expression (§12.12.1is

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 produkce odpovídá sekvenci znaků v kompilační jednotce, lexikální zpracování vždy tvoří 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. end example

Některé tokeny jsou definovány sadou lexikálních pravidel; hlavní pravidlo a jedno nebo více dílčích pravidel. Druhá se označí v gramatikě fragment , která označuje, ž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ář s oddělovači 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 komentář s oddělovači.

end example

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ářů.

end example

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 nejsou vnořené. 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 je komentář s oddělovači, který začíná před A koncem mezi B a C(). 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 oddělovaný komentář začínající před D koncem před E. Důvodem je, že "D */ " ve skutečnosti není řetězcový literál, protože počáteční znak dvojité uvozovky se zobrazí uvnitř komentáře s oddělovači.

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 správně zapouzdřuje oddělené komentáře v bloku, a obecně může zcela změnit strukturu těchto oddělených komentářů.

Příklad kódu:

static void Main()
{
    /* A
    // B */ C();
    Console.WriteLine(/* "D */ "E");
}

koncová poznámka

Single_Line_Comment s a Delimited_Commentmohou být použity jako komentáře k dokumentaci, jak je popsáno v §D.

6.3.4 Prázdné znaky

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

Řídicí sekvence znaků Unicode 6.4.2

Řídicí sekvence Unicode představuje bod kódu Unicode. Řídicí sekvence Unicode se zpracovávají v identifikátorech (§6.4.3), literály znaků (§6.4.5.5), normální řetězcové literály (§6.4.5.6) a interpolované regulární řetězcové výrazy (§12.8.3). Řídicí sekvence Unicode není zpracována v žádném jiném umístění (například pro vytvoření operátoru, interpunkční znaménečku 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
    ;

Řídicí sekvence znaků Unicode představuje jediný bod kódu Unicode, který je tvořen šestnáctkovým číslem 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+10000U+10FFFF reprezentován pomocí dvou náhradních jednotek kódu Unicode. Výše uvedené U+FFFF body kódu Unicode nejsou povoleny ve znakových literálech. Výše uvedené U+10FFFF body kódu Unicode jsou neplatné a nejsou podporovány.

Neprovádí se více překladů. 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 řídicí sekvence písmena "f". Program je ekvivalentní

class Class1
{
    static void Test(bool f)
    {
        char c = 'f';
        if (f)
        {
            System.Console.WriteLine(c.ToString());
        }
    }
}

end example

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ího tokenu, která se vytvoří 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 jako lexikálním pravidlům, která jsou před nimi uvedena.
  • Fragment Escaped_Identifier zahrnuje řídicí klíčová slova a kontextová klíčová slova, protože jsou součástí delšího tokenu počínaje @ a lexikálním zpracováním 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, _identifier2a @if. end example

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 vzhledem k tomu, že řídicí znaky Unicode nejsou povoleny v klíčových slovech, token "cl\u0061ss" je identifikátor a je stejný identifikátor jako "@class".

end example

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 jako identifikátor, a nelze ji použít jako identifikátor s výjimkou případů, kdy znak předčítá @ .

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@

contextual_keyword
    : 'add'    | 'alias'      | 'ascending' | 'async'     | 'await'
    | 'by'     | 'descending' | 'dynamic'   | 'equals'    | 'from'
    | 'get'    | 'global'     | 'group'     | 'into'      | 'join'
    | 'let'    | 'nameof'     | 'on'        | 'orderby'   | 'partial'
    | 'remove' | 'select'     | 'set'       | 'unmanaged' | 'value'
    | 'var'    | 'when'       | 'where'     | 'yield'
    ;

Poznámka: Klíčové slovo pravidla a contextual_keyword 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 get vlastnosti 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 určeno, jak mezi nimi nejednoznač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 tak, že je předponou 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 reprezentace 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í dvě logické hodnoty literálů: 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, , uintlong, ulong.
  • Pokud je literál příponou U nebo u, 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 nebo l, má první z těchto typů, ve kterých může být jeho hodnota reprezentována: long, ulong.
  • Pokud je literál příponou UL, , Ul, uLul, LU, LulU, nebo lu, je typu ulong.

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, abyL se při psaní literálů typu l"" 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 typu uint.
  • Pokud Integer_Literal představující hodnotu 9223372036854775808 (2⁶³) a žádný Integer_Type_Suffix nebo Integer_Type_SuffixL nebo l se zobrazí jako token bezprostředně za tokenem unárního operátoru minus (§12,9.3), je výsledek (obou tokenů) konstantou typu long s hodnotou −9223372036854775808 (−2⁶³). Ve všech ostatních situacích je takový Integer_Literal typu ulong.

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

end example

6.4.5.4 Skutečné literály

Skutečné literály se používají k zápisu hodnot typů float, doublea 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 skutečného literálu následujícím způsobem:

  • Skutečný literál s příponou F typu nebo f je typu float.

    Příklad: Literály 1f, 1.5f, 1e10fa 123.456F jsou všechny typu float. end example

  • Skutečný literál s příponou D typu nebo d je typu double.

    Příklad: Literály 1d, 1.5d, 1e10da 123.456D jsou všechny typu double. end example

  • Skutečný literál s příponou M typu nebo m je typu decimal.

    Příklad: Literály 1m, 1.5m, 1e10ma 123.456M jsou všechny typu decimal. end example
    Tento literál se převede na decimal hodnotu 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 zjevné v literálu se zachová, pokud se hodnota nezaokrouhlí. Poznámka: Proto literál 2.900m bude analyzován tak, aby formoval decimal se znaménkem 0, koeficientem 2900a měřítkem 3. 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 nevygeneruje nekonečno s plovoucí desetinou čárkou. 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 "round to nearest" (zaokrouhlit na nejbližší) se zalomenými na "sudé" (hodnota s nejméně významnou bitovou nulou) a všechny číslice 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. Jedná se například 1.3F o skutečný literál, ale 1.F není. koncová poznámka

Pří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

end example

6.4.5.5 Znakové literály

Literál znaku představuje jeden znak a skládá se z znaku v uvozovkách, jako 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. nrtuUxv V opačném případě dojde k chybě kompilace. koncová poznámka

Poznámka: Použití \xHexadecimal_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";

Může se nejprve zobrazit, že úvodní znak je stejný (U+0009znak tabulátoru) v obou řetězcích. Ve skutečnosti druhý řetězec začíná U+9BAD jako všechny tři písmena ve slově "Bad" jsou platné šestnáctkové číslice. Ve stylu se doporučuje, aby \x se zabránilo ve prospěch konkrétních řídicích sekvencí (\t v tomto příkladu) nebo řídicí sekvence s pevnou délkou \u .

koncová poznámka

Šestnáctková řídicí sekvence představuje jednu jednotku kódu Unicode UTF-16 s hodnotou vytvořenou šestnáctkovým číslem za "\x".

Pokud je hodnota reprezentovaná literálem znaku větší než U+FFFF, dojde k chybě v době kompilace.

Řídicí sekvence Unicode (§6.4.2) v literálu znaků musí být v rozsahu U+0000 do U+FFFF.

Jednoduchá řídicí sekvence představuje znak Unicode, jak je popsáno v tabulce níže.

Řídicí 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 Backspace U+0008
\f Informační kanál formuláře U+000C
\n Nový řádek U+000A
\r Návrat na začátek řádku 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 nuly nebo více znaků uzavřených v dvojitých uvozovkách, stejně jako v "hello", a může obsahovat jak jednoduché řídicí sekvence (například \t pro znak tabulátoru), tak šestnáctkové a unicode řídicí sekvence.

Doslovný řetězcový literál se skládá ze znaku @ následovaného znakem dvojité uvozovky, nuly nebo více znaků a pravou dvojitou uvozovkou.

Příklad: Jednoduchý příklad je @"hello". end example

Ve doslovné řetězcové literálu se znaky mezi oddělovači interpretují doslovně, přičemž jedinou výjimkou je Quote_Escape_Sequence, která představuje jeden znak s dvojitou uvozovkou. Zejména jednoduché řídicí sekvence a hexadecimální a řídicí sekvence Unicode 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 , jje 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.

end example

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 hodnotou 123. Chcete-li vytvořit řetězec obsahující znak s šestnáctkovou hodnotou 12 následovanou znakem 3, 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í nutně vést k nové instanci řetězce. Pokud jsou dva nebo více řetězcových literálů, které jsou ekvivalentní podle operátoru rovnosti řetězce (§12.12.8), zobrazí se 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.

end example

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

Null_literal představuje null hodnotu. 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 a b. end example

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 produkčních prostředí v syntaktické gramatikě nejsou mezi těmito dvěma tokeny v každém z těchto produkčních prostředí povoleny žádné znaky jakéhokoli druhu (ani prázdné znaky). 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 >>= oba tokeny. Syntaxe obecných typů ale používá < znaky k > oddělovači parametrů typu a argumentů typu. Často je žádoucí používat vnořené konstruované typy, například List<Dictionary<string, int>>. Místo toho, aby programátor oddělil > mezeru a > mezeru, změnila se definice dvou operator_or_punctuators. 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 nedefinu, v uvedeném pořadí, symboly podmíněné kompilace (§6.5.4).
  • #if, #elif, #elsea #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ů vygenerovaných pro chyby a upozornění (§6.5.8).
  • #error, která se používá k vydávání 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á čára obsahující #defineznak , #undef, #if#elif#else#endif#line, #endregion, nebo #nullable direktiva může končit jedním řádkem komentáře. 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é.

end example

6.5.2 Symboly podmíněné kompilace

Funkce podmíněné kompilace poskytované #ifdirektivami , #elif, #elsea #endif direktivy jsou řízeny 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 truea 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 před zpracováním mohou nastat v #if direktivách 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 ve výrazu předběžného zpracování má definovaný symbol podmíněné kompilace logickou hodnotu truea 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 Direktivy definice

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 se před prvním tokenem (§6.4) v kompilační jednotce; jinak dojde k chybě v době 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ází prvnímu tokenu ( namespace klíčovému slovu) v jednotce kompilace.

end example

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
}

end example

Znak #define podmíněné kompilace, který je již definován, může definovat bez jakéhokoli zásahu #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.

end example

Může #undef "zrušit definici" symbol 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

end example

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 pořadí, #if směrnice, nuly nebo více #elif direktiv, nuly nebo jedné #else směrnice a #endif směrnice. 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_Expression #if s direktiv a #elif direktiv se vyhodnocují v pořadí, dokud se nezíská true. Pokud se výraz zobrazí true, je vybrán podmíněný oddíl za odpovídající direktivou.
  • Pokud jsou všechny #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 vynechán zdrojový kód s výjimkou direktiv před zpracováním, lexikální nesprávná. Přeskočené direktivy předběžného zpracování jsou lexicky správné, ale jinak se nezpracují. V rámci podmíněného oddílu, který se přeskočí všechny vnořené podmíněné oddíly (obsažené v vnořených #if...#endif konstruktech), se také přeskočí.

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 direktivy podmíněné kompilace vnořit:

#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ý datový proud tokenu (classQ{} ), bez ohledu na to, zda je definován nebo není X definován. Pokud X je definována, jediné zpracované direktivy jsou #if a #endifvzhledem k víceřádkovému komentáři. Pokud X není definována, jsou tři direktivy (#if, #else, #endif) součástí sady direktiv.

end example

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 {...}

vytvoří chybu v době kompilace (sestavení nemůže být laděné i maloobchodní), pokud jsou definované symboly Debug podmíněné kompilace.Retail 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.

end example

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. Shody #region a #endregion direktivy mohou mít různé PP_Messages.

Lexikální zpracování oblasti:

#region
...
#endregion

odpovídá přesně lexikálnímu zpracování direktivy podmíněné kompilace formuláře:

#if true
...
#endif

Poznámka: To znamená, že oblast může obsahovat jednu nebo více #if/.../#endif, nebo může být obsažena s podmíněným oddílem v rámci #if/.../#endif; ale oblast se nemůže překrývat pouze s částí #if/.../#endif, nebo začít a končit v různých podmíněných oddílech. 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 a čísla řádků hlášená v chybových zprávách ani na základě použití CallerLineNumberAttribute§22.5.6.2). Má mít vliv na ladicí nástroje na úrovni zdroje, aby při ladění neměly všechny řádky mezi direktivou #line hidden a následnou #line direktivou (to není #line hidden) žádné informace o čísle řádku a při procházení kódu se zcela přeskočí.

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 Direktiva s možnou hodnotou Null

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 s možnou hodnotou null nastaví dostupné příznaky pro další řádky kódu, dokud ji nepřepíše jiná direktiva s možnou hodnotou null nebo dokud se nedosáhne konce kompilačního _unit . Kontext s možnou hodnotou null obsahuje dva příznaky: poznámky a upozornění. Účinek každé formy direktivy s možnou hodnotou null je následující:

  • #nullable disable: Zakáže jak poznámky s možnou hodnotou null, tak příznaky upozornění s možnou 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í příznaky poznámek i 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 poznámek s možnou hodnotou null do stavu určeného externím mechanismem, pokud existuje. 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 výrazů s možnou hodnotou null se sleduje vždy. 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 ("tak, jak x je null"). Stav s možnou x hodnotou null je sledován všude. Při povolení příznaku upozornění se zobrazí upozornění.

end example

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