Dela via


6 Lexikal struktur

6.1 Program

Ett C#-program består av en eller flera källfiler, formellt kallade kompileringsenheter (§14.2). Även om en kompileringsenhet kan ha en en-till-en-korrespondens med en fil i ett filsystem, krävs inte sådan korrespondens.

Konceptuellt sett kompileras ett program med tre steg:

  1. Transformering, som konverterar en fil från en viss teckenrepertoar och kodningsschema till en sekvens med Unicode-tecken.
  2. Lexikal analys, som översätter en ström av Unicode-indatatecken till en ström av token.
  3. Syntaktisk analys, som översätter strömmen av token till körbar kod.

Implementeringar som överensstämmer ska acceptera Unicode-kompileringsenheter som kodas med UTF-8-kodningsformuläret (enligt unicode-standarden) och omvandla dem till en sekvens med Unicode-tecken. Implementeringar kan välja att acceptera och transformera ytterligare teckenkodningsscheman (till exempel UTF-16, UTF-32 eller icke-Unicode-teckenmappningar).

Obs! Hanteringen av Unicode NULL-tecknet (U+0000) är implementeringsdefinierad. Vi rekommenderar starkt att utvecklare undviker att använda det här tecknet i källkoden för både portabilitet och läsbarhet. När tecknet krävs inom ett tecken eller strängliteral, kan escape-sekvenserna \0 eller \u0000 användas i stället. slutkommentar

Obs! Det ligger utanför omfånget för den här specifikationen att definiera hur en fil som använder en annan teckenrepresentation än Unicode kan omvandlas till en sekvens med Unicode-tecken. Under den här omvandlingen rekommenderar vi dock att det vanliga radavgränsande tecknet (eller sekvensen) i den andra teckenuppsättningen översätts till sekvensen med två tecken som består av Unicode-vagnreturtecknet (U+000D) följt av Unicode-radmatningstecken (U+000A). För det mesta har den här omvandlingen inga synliga effekter. Det påverkar dock tolkningen av ordagranna strängliteraltoken (§6.4.5.6). Syftet med den här rekommendationen är att tillåta en ordagrann strängliteral att producera samma teckensekvens när dess kompileringsenhet flyttas mellan system som stöder olika teckenuppsättningar som inte är Unicode-teckenuppsättningar, särskilt de som använder olika teckensekvenser för radseparation. slutkommentar

6.2 Grammatik

6.2.1 Allmänt

Den här specifikationen visar syntaxen för programmeringsspråket C# med hjälp av två grammatiker. Den lexikala grammatiken (§6.2.3) definierar hur Unicode-tecken kombineras för att bilda radavgränsare, blanksteg, kommentarer, token och förbearbetningsdirektiv. Den syntaktiska grammatiken (§6.2.4) definierar hur de token som följer av den lexikala grammatiken kombineras för att bilda C#-program.

Alla terminaltecken ska förstås som lämpligt Unicode-tecken från intervallet U+0020 till U+007F, i motsats till tecken som liknar varandra från andra Unicode-teckenintervall.

6.2.2 Grammatik notation

De lexikala och syntaktiska grammatikerna presenteras i ANTLR-grammatikverktygets extended backus-naur-form.

Även om ANTLR-notationen används, visar den här specifikationen inte en fullständig ANTLR-redo "referens grammatik" för C#; att skriva en lexer och parser, antingen för hand eller med hjälp av ett verktyg som ANTLR, ligger utanför omfånget för en språkspecifikation. Med den kvalifikationen försöker den här specifikationen minimera klyftan mellan den angivna grammatiken och den som krävs för att skapa en lexer och parser i ANTLR.

ANTLR skiljer mellan lexikal och syntaktisk, benämnd parser av ANTLR, grammatik i sin notation genom att starta lexikala regler med en versal bokstav och parserregler med en gemen bokstav.

Obs! Den lexikala C#-grammatiken (§6.2.3) och syntaktisk grammatik (§6.2.4) är inte i exakt korrespondens med ANTLR-divisionen i lexikala grammers och parser grammers. Det här lilla matchningsfelet innebär att vissa ANTLR-parserregler används när du anger den lexikala C#-grammatiken. slutkommentar

6.2.3 Lexikal grammatik

Den lexikala grammatiken för C# presenteras i §6.3, §6.4 och §6.5. Terminalsymbolerna för den lexikala grammatiken är tecknen i Unicode-teckenuppsättningen, och den lexikala grammatiken anger hur tecken kombineras till formtoken (§6.4), blanksteg (§6.3.4), kommentarer (§6.3.3) och förbearbetningsdirektiv (§6.5).

Många av terminalsymbolerna för den syntaktiska grammatiken definieras inte uttryckligen som token i den lexikala grammatiken. I stället utnyttjas ANTLR-beteendet att literalsträngar i grammatiken extraheras som implicita lexikala token. Detta gör att nyckelord, operatorer osv. kan representeras i grammatiken av deras literalrepresentation i stället för ett tokennamn.

Varje kompileringsenhet i ett C#-program ska överensstämma med den lexikala grammatikens indataproduktion (§6.3.1).

6.2.4 Syntaktisk grammatik

Den syntaktiska grammatiken för C# presenteras i de satser, underklisklar och bilagor som följer den här underclause. Terminalsymbolerna för den syntaktiska grammatiken är de token som uttryckligen definieras av den lexikala grammatiken och implicit av literalsträngar i själva grammatiken (§6.2.3). Den syntaktiska grammatiken anger hur token kombineras för att bilda C#-program.

Varje kompileringsenhet i ett C#-program ska överensstämma med den syntaktiska grammatikens compilation_unit produktion (§14.2).

6.2.5 Grammatik tvetydigheter

Produktionerna för simple_name (§12.8.4) och member_access (§12.8.7) kan ge löneförhöjning till tvetydigheter i grammatiken för uttryck.

Exempel: Instruktionen:

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

kan tolkas som ett anrop till F med två argument och G < AB > (7). Alternativt kan det tolkas som ett anrop till F med ett argument, vilket är ett anrop till en generisk metod G med två typargument och ett vanligt argument.

slutexempel

Om en sekvens av token kan parsas (i kontext) som en simple_name (§12.8.4), member_access (§12.8.7) eller pointer_member_access (§23.6.3) som slutar med en type_argument_list (§8.4.2), undersöks token omedelbart efter den avslutande token för att se om den är>

  • En av ( ) ] } : ; , . ? == != | ^ && || & [; eller
  • En av relationsoperatorerna < <= >= is as; eller
  • Ett kontextuellt frågenyckelord som visas i ett frågeuttryck. eller
  • I vissa sammanhang behandlas identifieraren som en tvetydig token. Dessa kontexter är där sekvensen av token som är tvetydiga omedelbart föregås av ett av nyckelorden is, case eller out, eller uppstår när du parsar det första elementet i en tuppelnliteral (i vilket fall token föregås av ( eller : och identifieraren följs av en ,) eller ett efterföljande element i en tuppelnliteral.

Om följande token finns i den här listan, eller en identifierare i en sådan kontext, behålls type_argument_list som en del av simple_name, member_access eller pointer_member åtkomst och eventuell parsning av tokensekvensen ignoreras. Annars anses type_argument_list inte vara en del av simple_name, member_access eller pointer_member_access, även om det inte finns någon annan möjlig parsning av tokensekvensen.

Obs! Dessa regler tillämpas inte vid parsning av en type_argument_list i en namespace_or_type_name (§7.8). slutkommentar

Exempel: Instruktionen:

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

tolkas enligt den här regeln som ett anrop till F med ett argument, vilket är ett anrop till en generisk metod G med två typargument och ett vanligt argument. Uttrycken

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

tolkas var och en som ett anrop till F med två argument. Instruktionen

x = F<A> + y;

tolkas som en operator som är mindre än, större än operatorn och unary-plus-operatorn, som om -instruktionen hade skrivits x = (F < A) > (+y), i stället för som en simple_name med en type_argument_list följt av en binär-plus-operator. I -instruktionen

x = y is C<T> && z;

token tolkas C<T> som en namespace_or_type_name med en type_argument_list på grund av förekomsten av den tvetydiga token && efter type_argument_list.

Uttrycket (A < B, C > D) är en tuppeln med två element, var och en en jämförelse.

Uttrycket (A<B,C> D, E) är en tuppeln med två element, varav det första är ett deklarationsuttryck.

Anropet M(A < B, C > D, E) har tre argument.

Anropet M(out A<B,C> D, E) har två argument, varav det första är en out deklaration.

Uttrycket e is A<B> C använder ett deklarationsmönster.

Ärendeetiketten case A<B> C: använder ett deklarationsmönster.

slutexempel

När du känner igen en relational_expression (§12.12.1is

6.3 Lexikal analys

6.3.1 Allmänt

För enkelhetens skull definierar och refererar den lexikala grammatiken till följande namngivna lexertoken:

DEFAULT  : 'default' ;
NULL     : 'null' ;
TRUE     : 'true' ;
FALSE    : 'false' ;
ASTERISK : '*' ;
SLASH    : '/' ;

Även om dessa är lexerregler stavas dessa namn i versaler för att skilja dem från vanliga lexerregelnamn.

Obs! Dessa bekvämlighetsregler är undantag från den vanliga metoden att inte tillhandahålla explicita tokennamn för token som definieras av literalsträngar. slutkommentar

Indataproduktionen definierar den lexikala strukturen för en C#-kompileringsenhet.

input
    : input_section?
    ;

input_section
    : input_section_part+
    ;

input_section_part
    : input_element* New_Line
    | PP_Directive
    ;

input_element
    : Whitespace
    | Comment
    | token
    ;

Obs! Ovanstående grammatik beskrivs av ANTLR-parsningsregler, den definierar den lexikala strukturen för en C#-kompileringsenhet och inte lexikala token. slutkommentar

Fem grundläggande element utgör den lexikala strukturen i en C#-kompileringsenhet: Linjeavgränsare (§6.3.2), blanksteg (§6.3.4), kommentarer (§6.3.3), token (§6.4) och förbearbetningsdirektiv (§6.5). Av dessa grundläggande element är endast token betydande i syntaktisk grammatik i ett C#-program (§6.2.4).

Den lexikala bearbetningen av en C#-kompileringsenhet består av att minska filen till en sekvens av token som blir indata till den syntaktiska analysen. Radavgränsare, blanksteg och kommentarer kan användas för att separera token, och förbearbetningsdirektiv kan leda till att delar av kompileringsenheten hoppas över, men annars har dessa lexikala element ingen inverkan på den syntaktiska strukturen i ett C#-program.

När flera lexikala grammatikproduktioner matchar en sekvens med tecken i en kompileringsenhet utgör den lexikala bearbetningen alltid det längsta möjliga lexikala elementet.

Exempel: Teckensekvensen // bearbetas som början på en kommentar med en rad eftersom det lexikala elementet är längre än en enskild / token. slutexempel

Vissa token definieras av en uppsättning lexikala regler. en huvudregel och en eller flera underregler. Det senare markeras i grammatiken genom fragment att ange att regeln definierar en del av en annan token. Fragmentregler beaktas inte i upp-till-botten-ordningen för lexikala regler.

Obs! I ANTLR fragment är ett nyckelord som ger samma beteende som definierats här. slutkommentar

6.3.2 Radavgränsare

Radavgränsare delar upp tecknen i en C#-kompileringsenhet i rader.

New_Line
    : New_Line_Character
    | '\u000D\u000A'    // carriage return, line feed 
    ;

För kompatibilitet med källkodsredigeringsverktyg som lägger till markörer i slutet av filen och för att göra det möjligt att se en kompileringsenhet som en sekvens med korrekt avslutade rader, tillämpas följande transformeringar för varje kompileringsenhet i ett C#-program:

  • Om kompileringsenhetens sista tecken är ett Control-Z-tecken (U+001A) tas det här tecknet bort.
  • Ett vagnreturtecken (U+000D) läggs till i slutet av kompileringsenheten om kompileringsenheten inte är tom och om kompileringsenhetens sista tecken inte är en vagnretur (U+000D), en radmatning (U+000A), ett nästa radtecken (U+0085), en radavgränsare (U+2028) eller en styckeavgränsare (U+2029).

Obs! Den extra vagnreturen gör att ett program kan sluta i en PP_Directive (§6.5) som inte har en avslutande New_Line. slutkommentar

6.3.3 Kommentarer

Två typer av kommentarer stöds: avgränsade kommentarer och kommentarer på en rad.

En avgränsad kommentar börjar med tecknen /* och slutar med tecknen */. Avgränsade kommentarer kan uppta en del av en rad, en enskild rad eller flera rader.

Exempel: Exemplet

/* Hello, world program
   This program writes "hello, world" to the console
*/
class Hello
{
    static void Main()
    {
        System.Console.WriteLine("hello, world");
    }
}

innehåller en avgränsad kommentar.

slutexempel

En kommentar med en rad börjar med tecknen // och sträcker sig till slutet av raden.

Exempel: Exemplet

// 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");
    }
}

visar flera enradskommentarer.

slutexempel

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
    ;

Kommentarer kapslas inte. Teckensekvenserna /* och */ har ingen särskild betydelse inom en enkelradskommentar och teckensekvenserna // och /* har ingen särskild betydelse inom en avgränsad kommentar.

Kommentarer bearbetas inte inom tecken- och strängliteraler.

Obs! Dessa regler måste tolkas noggrant. I exemplet nedan visas till exempel den avgränsade kommentaren som börjar innan A den slutar mellan B och C(). Anledningen är att

// B */ C();

är egentligen inte en enda rad kommentar, eftersom // har ingen särskild betydelse inom en avgränsad kommentar, och så */ har sin vanliga speciella betydelse i denna rad.

På samma sätt börjar den avgränsade kommentaren innan D den slutar före E. Anledningen är att det "D */ " faktiskt inte är en strängliteral, eftersom det första dubbla citattecknet visas i en avgränsad kommentar.

En användbar konsekvens av /* att och */ inte har någon särskild betydelse i en kommentar med en enda rad är att ett block med källkodsrader kan kommenteras ut genom att placera // i början av varje rad. I allmänhet fungerar det inte att sätta /* före dessa rader och */ efter dem, eftersom detta inte korrekt kapslar in avgränsade kommentarer i blocket, och i allmänhet kan helt ändra strukturen för sådana avgränsade kommentarer.

Exempelkod:

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

slutkommentar

Single_Line_Comment s och Delimited_Commenthar särskilda format kan användas som dokumentationskommenter, enligt beskrivningen i §D.

6.3.4 Tomt utrymme

Tomt utrymme definieras som alla tecken med Unicode-klass Zs (som innehåller blankstegstecknet) samt det vågräta fliktecknet, det lodräta tabbtecknet och formulärmatningstecknet.

Whitespace
    : [\p{Zs}]  // any character with Unicode class Zs
    | '\u0009'  // horizontal tab
    | '\u000B'  // vertical tab
    | '\u000C'  // form feed
    ;

6.4 Tokens

6.4.1 Allmänt

Det finns flera typer av token: identifierare, nyckelord, literaler, operatorer och skiljetecken. Tomt utrymme och kommentarer är inte token, även om de fungerar som avgränsare för token.

token
    : identifier
    | keyword
    | Integer_Literal
    | Real_Literal
    | Character_Literal
    | String_Literal
    | operator_or_punctuator
    ;

Obs! Det här är en ANTLR-parserregel, den definierar inte en lexikal token utan snarare en samling tokentyper. slutkommentar

6.4.2 Unicode-teckenrymningssekvenser

En Unicode-escape-sekvens representerar en Unicode-kodpunkt. Unicode escape-sekvenser bearbetas i identifierare (§6.4.3), teckenliteraler (§6.4.5.5), vanliga strängliteraler (§6.4.5.6) och interpolerade reguljära stränguttryck (§12.8.3). En Unicode-escape-sekvens bearbetas inte på någon annan plats (till exempel för att bilda en operator, skiljetecken eller nyckelord).

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
    ;

En Unicode-teckenrymningssekvens representerar den enda Unicode-kodpunkten som bildas av hexadecimalt tal efter tecknen "\u" eller "\U". Eftersom C# använder en 16-bitars kodning av Unicode-kodpunkter i tecken- och strängvärden representeras en Unicode-kodpunkt i intervallet U+10000 till U+10FFFF med hjälp av två Unicode-surrogatkodenheter. Unicode-kodpunkter ovan U+FFFF är inte tillåtna i teckenliteraler. Unicode-kodpunkterna ovan U+10FFFF är ogiltiga och stöds inte.

Flera översättningar utförs inte. Strängliteralen "\u005Cu005C" motsvarar till exempel i stället "\u005C"för "\" .

Obs! Unicode-värdet \u005C är tecknet "\". slutkommentar

Exempel: Exemplet

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

visar flera användningsområden för \u0066, vilket är escape-sekvensen för bokstaven "f". Programmet motsvarar

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

slutexempel

6.4.3 Identifierare

Reglerna för identifierare som anges i den här underklienten motsvarar exakt de som rekommenderas i Unicode Standard Bilaga 15, förutom att understreck tillåts som ett första tecken (vilket är traditionellt i C-programmeringsspråket), Unicode-escape-sekvenser tillåts i identifierare och "@"-tecknet tillåts som ett prefix för att göra det möjligt att använda nyckelord som identifierare.

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
    ;

Obs!

  • Information om unicode-teckenklasserna som nämns ovan finns i Unicode Standard.
  • Fragmentet Available_Identifier kräver undantag av nyckelord och kontextuella nyckelord. Om grammatiken i den här specifikationen bearbetas med ANTLR hanteras detta undantag automatiskt av semantiken i ANTLR:
    • Nyckelord och kontextuella nyckelord förekommer i grammatiken som literalsträngar.
    • ANTLR skapar implicita lexikala tokenregler skapas från dessa literalsträngar.
    • ANTLR beaktar dessa implicita regler före de explicita lexikala reglerna i grammatiken.
    • Fragment Available_Identifier matchar därför inte nyckelord eller kontextuella nyckelord som lexikala regler för dem som föregår det.
  • Fragment Escaped_Identifier innehåller undantagna nyckelord och kontextuella nyckelord eftersom de ingår i den längre token som börjar med en @ och lexikal bearbetning utgör alltid det längsta möjliga lexikala elementet (§6.3.1).
  • Hur en implementering tillämpar begränsningarna för tillåtna Unicode_Escape_Sequence värden är ett implementeringsproblem.

slutkommentar

Exempel: Exempel på giltiga identifierare är identifier1, _identifier2och @if. slutexempel

En identifierare i ett överensstämmande program ska vara i det kanoniska format som definieras av Unicode Normalization Form C, enligt definitionen i Unicode Standard Bilaga 15. Beteendet när du stöter på en identifierare som inte finns i normaliseringsformulär C är implementeringsdefinierad. En diagnostik krävs dock inte.

Prefixet "@" möjliggör användning av nyckelord som identifierare, vilket är användbart när du interagerar med andra programmeringsspråk. Tecknet @ är faktiskt inte en del av identifieraren, så identifieraren kan ses på andra språk som en normal identifierare, utan prefixet. En identifierare med ett @ prefix kallas för en ordagrann identifierare.

Obs! Användning av prefixet @ för identifierare som inte är nyckelord är tillåtet, men avråder starkt från stil. slutkommentar

Exempel: Exemplet:

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);
    }
}

definierar en klass med namnet "class" med en statisk metod med namnet "static" som tar en parameter med namnet "bool". Observera att eftersom Unicode-undantag inte tillåts i nyckelord är token "cl\u0061ss" en identifierare och är samma identifierare som "@class".

slutexempel

Två identifierare anses vara samma om de är identiska efter att följande transformeringar har tillämpats i ordning:

  • Prefixet "@", om det används, tas bort.
  • Varje Unicode_Escape_Sequence omvandlas till motsvarande Unicode-tecken.
  • Alla Formatting_Charactertas bort.

Semantiken för en identifierare med namnet _ beror på i vilken kontext den visas:

  • Det kan ange ett namngivet programelement, till exempel en variabel, klass eller metod, eller
  • Det kan beteckna en kassering (§9.2.9.2).

Identifierare som innehåller två på varandra följande understreckstecken (U+005F) är reserverade för användning av implementeringen, men ingen diagnostik krävs om en sådan identifierare definieras.

Obs! En implementering kan till exempel ge utökade nyckelord som börjar med två understreck. slutkommentar

6.4.4 Nyckelord

Ett nyckelord är en ID-liknande sekvens med tecken som är reserverad och kan inte användas som identifierare förutom när det @ föregås av tecknet.

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'
    ;

Ett kontextuellt nyckelord är en identifierliknande sekvens med tecken som har särskild betydelse i vissa kontexter, men som inte är reserverad, och som kan användas som identifierare utanför dessa kontexter samt när det @ föregås av tecknet.

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'
    ;

Obs! Nyckelordet ochcontextual_keyword är parsningsregler eftersom de inte introducerar nya tokentyper. Alla nyckelord och kontextuella nyckelord definieras av implicita lexikala regler eftersom de förekommer som literalsträngar i grammatiken (§6.2.3). slutkommentar

I de flesta fall är den syntaktiska platsen för kontextuella nyckelord sådan att de aldrig kan förväxlas med vanlig identifieraranvändning. I en egenskapsdeklaration har till exempel identifierarna och get den särskilda innebörden set (§15.7.3). En annan identifierare än get eller set tillåts aldrig på dessa platser, så den här användningen står inte i konflikt med användningen av dessa ord som identifierare.

I vissa fall räcker inte grammatiken för att skilja kontextuell nyckelordsanvändning från identifierare. I alla sådana fall anges hur de ska skiljas mellan de två. Det kontextuella nyckelordet var i implicit skrivna lokala variabeldeklarationer (§13.6.2) kan till exempel vara i konflikt med en deklarerad typ med namnet var, i vilket fall det deklarerade namnet har företräde framför användningen av identifieraren som ett kontextuellt nyckelord.

Ett annat exempel på en sådan tvetydighet är det kontextuella nyckelordet await (§12.9.8.1), som endast betraktas som ett nyckelord när det finns i en metod som deklarerats async, men som kan användas som identifierare någon annanstans.

Precis som med nyckelord kan kontextuella nyckelord användas som vanliga identifierare genom att prefixa dem med @ tecknet.

Obs! När de används som kontextuella nyckelord kan dessa identifierare inte innehålla Unicode_Escape_Sequences. slutkommentar

6.4.5 Literaler

6.4.5.1 Allmänt

En literal (§12.8.2) är en källkodsrepresentation av ett värde.

literal
    : boolean_literal
    | Integer_Literal
    | Real_Literal
    | Character_Literal
    | String_Literal
    | null_literal
    ;

Obs! Literal är en parserregel eftersom den grupperar andra tokentyper och inte introducerar någon ny tokentyp. slutkommentar

6.4.5.2 Booleska literaler

Det finns två booleska literalvärden: true och false.

boolean_literal
    : TRUE
    | FALSE
    ;

Obs! boolean_literal är en parserregel eftersom den grupperar andra tokentyper och inte introducerar någon ny tokentyp. slutkommentar

Typen av boolean_literal är bool.

6.4.5.3 Heltal

Heltalsliteraler används för att skriva värden av typerna int, uint, longoch ulong. Heltalsliteraler har tre möjliga former: decimaler, hexadecimala och binära.

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'
    ;

Typen av en heltalsliteral bestäms på följande sätt:

  • Om literalen inte har något suffix har den den första av dessa typer där dess värde kan representeras: int, uint, long, ulong.
  • Om literalen suffixas av U eller u, har den den första av dessa typer där dess värde kan representeras: uint, ulong.
  • Om literalen suffixas av L eller l, har den den första av dessa typer där dess värde kan representeras: long, ulong.
  • Om literalen är suffixet av UL, Ul, uL, ul, LU, Lu, , lUeller lu, är det av typen ulong.

Om värdet som representeras av en heltalsliteral ligger utanför typintervallet ulong uppstår ett kompileringsfel.

Obs! Det rekommenderas att "L" används i stället för "l" när du skriver literaler av typen long, eftersom det är lätt att förväxla bokstaven "l" med siffran "1". slutkommentar

För att tillåta att minsta möjliga int värden och long värden skrivs som heltalsliteraler finns följande två regler:

  • När en Integer_Literal som representerar värdet 2147483648 (2³¹) och ingen Integer_Type_Suffix visas som token omedelbart efter en unary minus operatortoken (§12.9.3), är resultatet (av båda token) en konstant typint med värdet −2147483648 (−2³¹). I alla andra situationer är en sådan Integer_Literal av typen uint.
  • När en Integer_Literal som representerar värdet 9223372036854775808 (2⁶³) och ingen Integer_Type_Suffix eller Integer_Type_SuffixL eller l visas som token omedelbart efter en unary minus operatortoken (§12.9.3), är resultatet (av båda token) en konstant av typen long med värdet −9223372036854775808 (−2⁶³). I alla andra situationer är en sådan Integer_Literal av typen ulong.

Exempel:

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

slutexempel

6.4.5.4 Verkliga literaler

Verkliga literaler används för att skriva värden av typerna float, doubleoch 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'
    ;

Om ingen Real_Type_Suffix anges är typen av double . I annat fall avgör Real_Type_Suffix typen av den verkliga literalen enligt följande:

  • Ett verkligt literalsuffix av F eller f är av typen float.

    Exempel: Literalerna 1f, 1.5f, 1e10foch 123.456F är alla av typen float. slutexempel

  • Ett verkligt literalsuffix av D eller d är av typen double.

    Exempel: Literalerna 1d, 1.5d, 1e10doch 123.456D är alla av typen double. slutexempel

  • Ett verkligt literalsuffix av M eller m är av typen decimal.

    Exempel: Literalerna 1m, 1.5m, 1e10moch 123.456M är alla av typen decimal. slutexempel
    Den här literalen konverteras till ett decimal värde genom att det exakta värdet tas och vid behov avrundas till närmaste representerbara värde med bankirens avrundning (§8.3.8). Alla skalbara värden i literalen bevaras såvida inte värdet avrundas. Obs! Därför parsas literalen 2.900mdecimal för att bilda med -tecknet 0, koefficienten 2900och skala 3. slutkommentar

Om storleken på den angivna literalen är för stor för att representeras i den angivna typen uppstår ett kompileringsfel.

Obs! I synnerhet kommer en Real_Literal aldrig att skapa en oändlighet för flyttalser. En Real_Literal som inte är noll kan dock avrundas till noll. slutkommentar

Värdet för en verklig literal av typen float eller double bestäms med hjälp av IEC 60559-läget "avrunda till närmaste" med band brutna till "jämna" (ett värde med den minst signifikanta biten noll) och alla siffror som anses vara betydande.

Obs! I en verklig literal krävs alltid decimalsiffror efter decimaltecknet. Till exempel är en riktig literal men 1.3F1.F är inte det. slutkommentar

Exempel:

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

slutexempel

6.4.5.5 Teckenliteraler

En teckenliteral representerar ett enskilt tecken och består av ett tecken inom citattecken, som i '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?
    ;

Obs! Ett tecken som följer ett omvänt snedstreck (\) i ett tecken måste vara något av följande tecken: ', , "\0, , a, b, fn, , r, t, u, U, , x. v Annars uppstår ett kompileringsfel. slutkommentar

Obs! Användningen av Hexadecimal_Escape_Sequence\xproduktion kan vara felbenägen och svår att läsa på grund av det varierande antalet hexadecimala siffror efter .\x I koden till exempel:

string good = "\x9Good text";
string bad = "\x9Bad text";

Det kan först visas att det inledande tecknet är samma (U+0009, ett fliktecken) i båda strängarna. I själva verket börjar den andra strängen med U+9BAD eftersom alla tre bokstäverna i ordet "Bad" är giltiga hexadecimala siffror. Som en fråga om stil rekommenderas att \x undvikas till förmån för antingen specifika escape-sekvenser (\t i det här exemplet) eller escape-sekvensen med fast längd \u .

slutkommentar

En hexadecimal escape-sekvens representerar en enda Unicode UTF-16-kodenhet, med värdet som bildas av hexadecimalt tal efter "\x".

Om värdet som representeras av en teckenliteral är större än U+FFFFuppstår ett kompileringsfel.

En Unicode-escape-sekvens (§6.4.2) i en teckenliteral ska ligga i intervallet U+0000 till U+FFFF.

En enkel escape-sekvens representerar ett Unicode-tecken enligt beskrivningen i tabellen nedan.

Escape-sekvens Teckennamn Unicode-kodpunkt
\' Enkelt citat U+0027
\" Dubbelt citat U+0022
\\ Omvänt snedstreck U+005C
\0 Null U+0000
\a Varning U+0007
\b Backstegstangent U+0008
\f Formulärfeed U+000C
\n Ny rad U+000A
\r Vagnretur U+000D
\t Vågrät flik U+0009
\v Lodrät flik U+000B

Typen av en Character_Literal är char.

6.4.5.6 Strängliteraler

C# stöder två former av strängliteraler: vanliga strängliteraler och ordagranna strängliteraler. En vanlig strängliteral består av noll eller fler tecken som omges av dubbla citattecken, som i "hello", och kan innehålla både enkla escape-sekvenser (till exempel \t för tabbtecknet) och hexadecimala och Unicode-escape-sekvenser.

En ordagrann strängliteral består av ett @ tecken följt av ett dubbelcitattecken, noll eller fler tecken och ett avslutande dubbelcitattecken.

Exempel: Ett enkelt exempel är @"hello". slutexempel

I en ordagrann strängliteral tolkas tecknen mellan avgränsarna ordagrant, med det enda undantaget en Quote_Escape_Sequence, som representerar ett dubbelcitattecken. I synnerhet bearbetas inte enkla escape-sekvenser och hexadecimala och Unicode-escape-sekvenser i ordagranna strängliteraler. En ordagrann strängliteral kan sträcka sig över flera rader.

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
    : '""'
    ;

Exempel: Exemplet

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";

visar en mängd olika strängliteraler. Den sista strängliteralen, j, är en ordagrann strängliteral som sträcker sig över flera rader. Tecknen mellan citattecknen, inklusive blanksteg som nya radtecken, bevaras ordagrant och varje par med dubbla citattecken ersätts med ett sådant tecken.

slutexempel

Obs! Alla radbrytningar inom ordagranna strängliteraler är en del av den resulterande strängen. Om de exakta tecken som används för att bilda radbrytningar är semantiskt relevanta för ett program, kommer alla verktyg som översätter radbrytningar i källkod till olika format (mellan "\n" och "\r\n", till exempel) att ändra programmets beteende. Utvecklare bör vara försiktiga i sådana situationer. slutkommentar

Obs! Eftersom en hexadecimal escape-sekvens kan ha ett variabelt antal hexsiffror innehåller strängliteralen "\x123" ett enda tecken med hexvärdet 123. Om du vill skapa en sträng som innehåller tecknet med hexvärde 12 följt av tecknet 3kan man skriva "\x00123" eller "\x12" + "3" i stället. slutkommentar

Typen av en String_Literal är string.

Varje strängliteral resulterar inte nödvändigtvis i en ny stränginstans. När två eller flera strängliteraler som är likvärdiga enligt strängjämlikhetsoperatorn (§12.12.8) visas i samma sammansättning refererar dessa strängliteraler till samma stränginstans.

Exempel: Till exempel de utdata som genereras av

class Test
{
    static void Main()
    {
        object a = "hello";
        object b = "hello";
        System.Console.WriteLine(a == b);
    }
}

beror True på att de två literalerna refererar till samma stränginstans.

slutexempel

6.4.5.7 Nullliteralen

null_literal
    : NULL
    ;

Obs! null_literal är en parserregel eftersom den inte introducerar någon ny tokentyp. slutkommentar

En null_literal representerar ett null värde. Den har ingen typ, men kan konverteras till en referenstyp eller nullbar värdetyp genom en nullliteralkonvertering (§10.2.7).

6.4.6 Operatorer och skiljetecken

Det finns flera typer av operatorer och skiljetecken. Operatorer används i uttryck för att beskriva åtgärder som involverar en eller flera operander.

Exempel: Uttrycket a + b använder operatorn + för att lägga till de två operanderna a och b. slutexempel

Skiljetecken är till för att gruppera och separera.

operator_or_punctuator
    : '{'  | '}'  | '['  | ']'  | '('   | ')'  | '.'  | ','  | ':'  | ';'
    | '+'  | '-'  | ASTERISK    | SLASH | '%'  | '&'  | '|'  | '^'  | '!' | '~'
    | '='  | '<'  | '>'  | '?'  | '??'  | '::' | '++' | '--' | '&&' | '||'
    | '->' | '==' | '!=' | '<=' | '>='  | '+=' | '-=' | '*=' | '/=' | '%='
    | '&=' | '|=' | '^=' | '<<' | '<<=' | '=>'
    ;

right_shift
    : '>'  '>'
    ;

right_shift_assignment
    : '>' '>='
    ;

Obs! right_shift och right_shift_assignment är parsningsregler eftersom de inte introducerar en ny tokentyp utan representerar en sekvens med två token. Regeln operator_or_punctuator finns endast i beskrivande syfte och används inte någon annanstans i grammatiken. slutkommentar

right_shift består av de två token och >>. På samma sätt består right_shift_assignment av de två token och >>=. Till skillnad från andra produktioner i syntaktisk grammatik tillåts inga tecken av något slag (inte ens blanksteg) mellan de två tokenerna i var och en av dessa produktioner. Dessa produktioner behandlas särskilt för att möjliggöra korrekt hantering av type_parameter_lists (§15.2.3).

Obs! Före tillägget av generiska objekt i C#, >> och >>= var båda enskilda token. Syntaxen för generiska objekt använder < dock tecknen och > för att avgränsa typparametrar och typargument. Det är ofta önskvärt att använda kapslade konstruerade typer, till exempel List<Dictionary<string, int>>. I stället för att kräva att programmeraren separerar > och > efter ett utrymme ändrades definitionen av de två operator_or_punctuators. slutkommentar

6.5 Förbehandlingsdirektiv

6.5.1 Allmänt

Förbearbetningsdirektiven ger möjlighet att villkorligt hoppa över delar av kompileringsenheter, rapportera fel- och varningsvillkor, avgränsa distinkta regioner med källkod och ange den nullbara kontexten.

Obs! Termen "förbearbetningsdirektiv" används endast för konsekvens med programmeringsspråken C och C++. I C# finns det inget separat förbearbetningssteg. förbearbetningsdirektiv behandlas som en del av den lexikala analysfasen. slutkommentar

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
    ;

Obs!

  • Grammatik före processorn definierar en enda lexikal token PP_Directive som används för alla förbearbetningsdirektiv. Semantiken för vart och ett av förbehandlingsdirektiven definieras i den här språkspecifikationen, men inte hur de ska implementeras.
  • Fragmentet PP_Start får bara erkännas i början av en rad, getCharPositionInLine() == 0 antLR lexikal predikat ovan föreslår ett sätt på vilket detta kan uppnås och är endast informativt, ett genomförande kan använda en annan strategi.

slutkommentar

Följande förbearbetningsdirektiv är tillgängliga:

  • #define och #undef, som används för att definiera respektive odefiniera villkorsstyrda kompileringssymboler (§6.5.4).
  • #if, #elif, #else, och #endif, som används för att hoppa över villkorliga avsnitt i källkoden (§6.5.5).
  • #line, som används för att styra radnummer som genereras för fel och varningar (§6.5.8).
  • #error, som används för att utfärda fel (§6.5.6).
  • #region och #endregion, som används för att uttryckligen markera delar av källkoden (§6.5.7).
  • #nullable, som används för att ange den nullbara kontexten (§6.5.9).
  • #pragma, som används för att ange valfri sammanhangsinformation för en kompilator (§6.5.10).

Ett förbearbetningsdirektiv upptar alltid en separat rad källkod och börjar alltid med ett # tecken och ett förbehandlingsdirektivnamn. Tomt utrymme kan uppstå före # tecknet och mellan # tecknet och direktivnamnet.

En källrad som innehåller ett #define, #undef, #if, #elif, #else#endif, #line, , #endregioneller #nullable -direktiv kan sluta med en enskild radkommentar. Avgränsade kommentarer (kommentarsformatet /* */ ) är inte tillåtna på källrader som innehåller förbearbetningsdirektiv.

Förbearbetningsdirektiv är inte en del av den syntaktiska grammatiken i C#. Förbearbetningsdirektiv kan dock användas för att inkludera eller exkludera sekvenser av token och kan på så sätt påverka innebörden av ett C#-program.

Exempel: När programmet kompileras

#define A
#undef B
class C
{
#if A
    void F() {}
#else
    void G() {}
#endif
#if B
    void H() {}
#else    
    void I() {}
#endif
}

resulterar i exakt samma sekvens av token som programmet

class C
{
    void F() {}
    void I() {}
}

Sålunda, medan lexikaliskt, de två programmen är helt olika, syntaktiskt, de är identiska.

slutexempel

6.5.2 Villkorsstyrda kompileringssymboler

Den villkorliga kompileringsfunktionen som tillhandahålls av direktiven , , och styrs genom förbearbetningsuttryck (#if) och villkorsstyrda kompileringssymboler.#elif#else#endif

fragment PP_Conditional_Symbol
    // Must not be equal to tokens TRUE or FALSE. See note below.
    : Basic_Identifier
    ;

Obs! Hur en implementering framtvingar begränsningen av tillåtna Basic_Identifier värden är ett implementeringsproblem. slutkommentar

Två villkorsstyrda kompileringssymboler anses vara desamma om de är identiska efter att följande transformeringar har tillämpats i ordning:

  • Varje Unicode_Escape_Sequence omvandlas till motsvarande Unicode-tecken.
  • Alla Formatting_Characters tas bort.

En villkorlig kompileringssymbol har två möjliga tillstånd: definierade eller odefinierade. I början av den lexikala bearbetningen av en kompileringsenhet är en villkorsstyrd kompileringssymbol odefinierad om den inte uttryckligen har definierats av en extern mekanism (till exempel ett kommandoradskompilatoralternativ). När ett #define direktiv bearbetas definieras den villkorsstyrda kompileringssymbolen med namnet i det direktivet i den kompileringsenheten. Symbolen förblir definierad tills ett #undef direktiv för samma symbol bearbetas eller till slutet av kompileringsenheten har nåtts. En konsekvens av detta är att #define direktiv #undef i en kompileringsenhet inte har någon effekt på andra kompileringsenheter i samma program.

När det refereras i ett förbearbetningsuttryck (§6.5.3) har en definierad villkorlig kompileringssymbol det booleska värdet trueoch en odefinierad villkorskompileringssymbol har det booleska värdet false. Det finns inget krav på att villkorsstyrda kompileringssymboler uttryckligen deklareras innan de refereras i förbearbetningsuttryck. I stället är odeklarerade symboler helt enkelt odefinierade och har därmed värdet false.

Namnområdet för villkorsstyrda kompileringssymboler är distinkt och separat från alla andra namngivna entiteter i ett C#-program. Villkorsstyrda kompileringssymboler kan bara refereras till i #define direktiv och #undef i förbearbetningsuttryck.

6.5.3 Förbearbetningsuttryck

Förbearbetningsuttryck kan förekomma i #if och #elif direktiv. Operatorerna ! (endast logisk prefix negation), ==, !=, &&och || tillåts i förbearbetningsuttryck och parenteser kan användas för gruppering.

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? ')'
    ;

När det refereras till i ett förbearbetningsuttryck har en definierad villkorskompileringssymbol det booleska värdet trueoch en odefinierad villkorskompileringssymbol har det booleska värdet false.

Utvärdering av ett förbearbetningsuttryck ger alltid ett booleskt värde. Utvärderingsreglerna för ett förbearbetningsuttryck är desamma som för ett konstant uttryck (§12.23), förutom att de enda användardefinierade entiteterna som kan refereras är villkorsstyrda kompileringssymboler.

6.5.4 Definitionsdirektiv

Definitionsdirektiven används för att definiera eller odefiniera villkorsstyrda kompileringssymboler.

fragment PP_Declaration
    : 'define' PP_Whitespace PP_Conditional_Symbol
    | 'undef' PP_Whitespace PP_Conditional_Symbol
    ;

Bearbetningen av ett #define direktiv gör att den givna villkorsstyrda kompileringssymbolen definieras, med början i källlinjen som följer direktivet. På samma sätt gör bearbetningen av ett #undef direktiv att den givna villkorsstyrda kompileringssymbolen blir odefinierad, med början i källlinjen som följer av direktivet.

Alla #define och #undef direktiv i en kompileringsenhet ska ske före den första token (§6.4) i kompileringsenheten, annars uppstår ett kompileringsfel. Intuitivt sett #define ska direktiven #undef föregå all "verklig kod" i kompileringsenheten.

Exempel: Exemplet:

#define Enterprise
#if Professional || Enterprise
#define Advanced
#endif
namespace Megacorp.Data
{
#if Advanced
    class PivotTable {...}
#endif
}

är giltigt eftersom direktiven #define föregår den första token (nyckelordet namespace ) i kompileringsenheten.

slutexempel

Exempel: Följande exempel resulterar i ett kompileringsfel eftersom en #define följer verklig kod:

#define A
namespace N
{
#define B
#if B
    class Class1 {}
#endif
}

slutexempel

A #define kan definiera en villkorsstyrd kompileringssymbol som redan har definierats, utan att det finns något mellanliggande #undef för den symbolen.

Exempel: Exemplet nedan definierar en villkorsstyrd kompileringssymbol A och definierar den sedan igen.

#define A
#define A

För kompilatorer som tillåter att villkorsstyrda kompileringssymboler definieras som kompileringsalternativ är ett alternativt sätt att definiera omdefinitionen att definiera symbolen som ett kompilatoralternativ samt i källan.

slutexempel

A #undef kan "odefiniera" en villkorsstyrd kompileringssymbol som inte har definierats.

Exempel: Exemplet nedan definierar en villkorsstyrd kompileringssymbol A och odefinierar den två gånger. Även om den andra #undef inte har någon effekt är den fortfarande giltig.

#define A
#undef A
#undef A

slutexempel

6.5.5 Villkorliga kompileringsdirektiv

Direktiven för villkorlig kompilering används för att villkorligt inkludera eller exkludera delar av en kompileringsenhet.

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'
    ;

Direktiv om villkorsstyrd kompilering skall vara skrivna i grupper som består av ett #if direktiv, noll eller flera #elif direktiv, noll eller ett #else direktiv och ett #endif direktiv. Mellan direktiven finns villkorsstyrda delar av källkoden. Varje avsnitt styrs av det omedelbart föregående direktivet. Ett villkorligt avsnitt kan i sig innehålla kapslade direktiv för villkorlig kompilering, förutsatt att dessa direktiv utgör fullständiga grupper.

Högst en av de inneslutna villkorliga avsnitten väljs för normal lexikal bearbetning:

  • De PP_Expressionav direktiven #if och #elif utvärderas i ordning tills en ger true. Om ett uttryck ger väljs villkorsavsnittet truesom följer motsvarande direktiv.
  • Om alla PP_Expressionavkastning false, och om ett #else direktiv finns, väljs villkorsavsnittet #else som följer direktivet.
  • Annars är inget villkorsavsnitt markerat.

Det valda villkorsavsnittet, om det finns något, bearbetas som ett normalt input_section: källkoden i avsnittet ska följa den lexikala grammatiken, token genereras från källkoden i avsnittet och förbearbetningsdirektiven i avsnittet har de föreskrivna effekterna.

Eventuella återstående villkorsstyrda avsnitt hoppas över och inga token, förutom de för förbearbetningsdirektiv, genereras från källkoden. Därför kan den överhoppade källkoden, förutom förbearbetningsdirektiv, vara lexikalt felaktig. Överhoppade förbearbetningsdirektiv ska vara lexikaliskt korrekta men bearbetas inte på annat sätt. I ett villkorsavsnitt som hoppas över alla kapslade villkorsdelar (som finns i kapslade #if...#endif konstruktioner) hoppas de också över.

Obs! Grammatiken ovan tar inte hänsyn till att de villkorsstyrda avsnitten mellan förbearbetningsdirektiven kan vara felaktigt formade lexikaliskt. Därför är grammatiken inte ANTLR-klar eftersom den endast stöder lexikaliskt korrekta indata. slutkommentar

Exempel: I följande exempel visas hur villkorliga kompileringsdirektiv kan kapslas:

#define Debug // Debugging on
#undef Trace // Tracing off
class PurchaseTransaction
{
    void Commit()
    {
#if Debug
        CheckConsistency();
    #if Trace
        WriteToLog(this.ToString());
    #endif
#endif
        CommitHelper();
    }
    ...
}

Förutom förbearbetningsdirektiv är överhoppad källkod inte föremål för lexikal analys. Följande är till exempel giltigt trots den obestämda kommentaren #else i avsnittet:

#define Debug // Debugging on
class PurchaseTransaction
{
    void Commit()
    {
#if Debug
        CheckConsistency();
#else
        /* Do something else
#endif
    }
    ...
}

Observera dock att förbearbetningsdirektiv måste vara lexikaliskt korrekta även i överhoppade delar av källkoden.

Förbearbetningsdirektiv bearbetas inte när de visas i indataelement med flera rader. Till exempel programmet:

class Hello
{
    static void Main()
    {
        System.Console.WriteLine(@"hello,
#if Debug
        world
#else
        Nebraska
#endif
        ");
    }
}

resulterar i utdata:

hello,
#if Debug
        world
#else
        Nebraska
#endif

I vissa fall kan den uppsättning förbearbetningsdirektiv som bearbetas bero på utvärderingen av pp_expression. Exemplet:

#if X
    /*
#else
    /* */ class Q { }
#endif

genererar alltid samma tokenström (classQ{} ), oavsett om den har definierats eller inte.X Om X definieras är #if de enda bearbetade direktiven och #endif, på grund av kommentaren med flera rader. Om X är odefinierat ingår tre direktiv (#if, #else, #endif) i direktivuppsättningen.

slutexempel

6.5.6 Diagnostikdirektiv

Diagnostikdirektiven används för att generera explicita fel- och varningsmeddelanden som rapporteras på samma sätt som andra kompileringsfel och varningar.

fragment PP_Diagnostic
    : 'error' PP_Message?
    | 'warning' PP_Message?
    ;

fragment PP_Message
    : PP_Whitespace Input_Character*
    ;

Exempel: Exemplet

#if Debug && Retail
    #error A build can't be both debug and retail
#endif
class Test {...}

skapar ett kompileringsfel ("En version kan inte både felsökas och säljas") om de villkorsstyrda kompileringssymbolerna Debug och Retail båda definieras. Observera att en PP_Message kan innehålla godtycklig text. Mer specifikt behöver den inte innehålla välformulerad token, vilket visas av det enda citattecken i ordet can't.

slutexempel

6.5.7 Regiondirektiv

Regiondirektiven används för att uttryckligen markera källkodsregioner.

fragment PP_Region
    : PP_Start_Region
    | PP_End_Region
    ;

fragment PP_Start_Region
    : 'region' PP_Message?
    ;

fragment PP_End_Region
    : 'endregion' PP_Message?
    ;

Ingen semantisk betydelse är kopplad till en region. regioner är avsedda att användas av programmeraren eller av automatiserade verktyg för att markera ett avsnitt i källkoden. Det ska finnas ett direktiv som #endregion matchar varje #region direktiv. Meddelandet som anges i ett #region eller #endregion -direktiv har inte heller någon semantisk betydelse. Det tjänar bara till att identifiera regionen. Matchning #region och #endregion direktiv kan ha olika PP_Messages.

Den lexikala behandlingen av en region:

#region
...
#endregion

motsvarar exakt den lexikala behandlingen av ett villkorligt kompileringsdirektiv i formuläret:

#if true
...
#endif

Obs! Det innebär att en region kan innehålla en eller flera #if/.../#endif, eller vara innesluten i ett villkorsavsnitt inom ett #if/.../#endif; men en region kan inte överlappa med en del av en #if/.../#endif, eller starta och sluta i olika villkorsavsnitt. slutkommentar

6.5.8 Linjedirektiv

Linjedirektiv kan användas för att ändra radnummer och samlingsenhetsnamn som rapporteras av en kompilator i utdata, till exempel varningar och fel. Dessa värden används också av caller-info-attribut (§22.5.6).

Obs! Linjedirektiv används oftast i metaprogrammeringsverktyg som genererar C#-källkod från andra textinmatningar. slutkommentar

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' | '"')
    ;

När det inte finns några #line direktiv rapporterar en kompilator sanna radnummer och samlingsenhetsnamn i sina utdata. Vid bearbetning av ett #line direktiv som innehåller en PP_Line_Indicator som inte är defaultbehandlar en kompilator raden efter direktivet som att ha det angivna radnumret (och kompileringsenhetsnamnet, om det anges).

Det högsta tillåtna värdet för Decimal_Digit+ är implementeringsdefinierat.

Ett #line default direktiv upphäver effekten av alla föregående #line direktiv. En kompilator rapporterar sann linjeinformation för efterföljande rader, precis som om inga #line direktiv hade bearbetats.

Ett #line hidden direktiv har ingen effekt på kompileringsenheten och radnummer som rapporteras i felmeddelanden eller som produceras med hjälp av CallerLineNumberAttribute (§22.5.6.2). Den är avsedd att påverka felsökningsverktyg på källnivå så att alla rader mellan ett #line hidden direktiv och det efterföljande #line direktivet (dvs. inte #line hidden) inte har någon radnummerinformation och hoppas över helt när de går igenom koden.

Obs! Även om en PP_Compilation_Unit_Name kan innehålla text som ser ut som en escape-sekvens är sådan text inte en escape-sekvens. I det här sammanhanget anger ett "\" tecken helt enkelt ett vanligt omvänt snedstreck. slutkommentar

6.5.9 Direktiv som kan upphävas

Det nullbara direktivet styr den nullbara kontexten enligt beskrivningen nedan.

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'
    ;

Ett null-direktiv anger tillgängliga flaggor för efterföljande kodrader tills ett annat null-direktiv åsidosätter det eller tills slutet av kompilerings- _unit har nåtts. Den nullbara kontexten innehåller två flaggor: anteckningar och varningar. Effekten av varje form av null-direktiv är följande:

  • #nullable disable: Inaktiverar både ogiltiga anteckningar och nullbara varningsflaggor.
  • #nullable enable: Aktiverar både ogiltiga anteckningar och nullbara varningsflaggor.
  • #nullable restore: Återställer både anteckningsflaggor och varningsflaggor till det tillstånd som anges av den externa mekanismen, om det finns några.
  • #nullable disable annotations: Inaktiverar flaggan för ogiltiga anteckningar. Flaggan nullable warnings (nullable warnings) påverkas inte.
  • #nullable enable annotations: Aktiverar flaggan för ogiltiga anteckningar. Flaggan nullable warnings (nullable warnings) påverkas inte.
  • #nullable restore annotations: Återställer flaggan nullable annotations till det tillstånd som anges av den externa mekanismen, om någon. Flaggan nullable warnings (nullable warnings) påverkas inte.
  • #nullable disable warnings: Inaktiverar den nullbara varningsflaggan. Flaggan för null-anteckningar påverkas inte.
  • #nullable enable warnings: Aktiverar flaggan för null-varningar. Flaggan för null-anteckningar påverkas inte.
  • #nullable restore warnings: Återställer flaggan nullable warnings till det tillstånd som anges av den externa mekanismen, om någon. Flaggan för null-anteckningar påverkas inte.

Det nullbara tillståndet för uttryck spåras hela tiden. Status för anteckningsflaggan och förekomsten eller frånvaron av en nullbar anteckning, ?, avgör det inledande null-tillståndet för en variabeldeklaration. Varningar utfärdas endast när varningsflaggan är aktiverad.

Exempel: Exemplet

#nullable disable
string x = null;
string y = "";
#nullable enable
Console.WriteLine(x.Length); // Warning
Console.WriteLine(y.Length);

skapar en kompileringstidsvarning ("som x är null"). Det nullbara tillståndet x för spåras överallt. En varning utfärdas när varningsflaggan är aktiverad.

slutexempel

6.5.10 Pragma-direktiv

Förbearbetningsdirektivet #pragma används för att ange kontextuell information till en kompilator.

Obs! En kompilator kan till exempel tillhandahålla #pragma direktiv som

  • Aktivera eller inaktivera specifika varningsmeddelanden vid kompilering av efterföljande kod.
  • Ange vilka optimeringar som ska tillämpas på efterföljande kod.
  • Ange information som ska användas av ett felsökningsprogram.

slutkommentar

fragment PP_Pragma
    : 'pragma' PP_Pragma_Text?
    ;

fragment PP_Pragma_Text
    : PP_Whitespace Input_Character*
    ;

Input_Charactertolkas i PP_Pragma_Text av en kompilator på ett implementeringsdefinierat sätt. Den information som lämnas i ett #pragma direktiv får inte ändra programmets semantik. Ett #pragma direktiv ska endast ändra kompilatorbeteendet som ligger utanför den här språkspecifikationens omfång. Om en kompilator inte kan tolka Input_Characters kan en kompilator skapa en varning. Det får dock inte leda till ett kompileringsfel.

Obs! PP_Pragma_Text kan innehålla godtycklig text. Mer specifikt behöver den inte innehålla välformulerad token. slutkommentar