6 Lexicale structuur
6.1 Programma's
Een C#-programma bestaat uit een of meer bronbestanden, formeel bekend als compilatie-eenheden (§14.2). Hoewel een compilatie-eenheid mogelijk een een-op-een-correspondentie met een bestand in een bestandssysteem heeft, is dergelijke correspondentie niet vereist.
Conceptueel gesproken wordt een programma gecompileerd met behulp van drie stappen:
- Transformatie, waarmee een bestand wordt geconverteerd van een bepaald tekenperpertoire en coderingsschema in een reeks Unicode-tekens.
- Lexicale analyse, waarmee een stroom Unicode-invoertekens wordt omgezet in een stroom tokens.
- Syntactische analyse, waarmee de stroom tokens wordt omgezet in uitvoerbare code.
Conforme implementaties accepteert u Unicode-compilatie-eenheden die zijn gecodeerd met het UTF-8-coderingsformulier (zoals gedefinieerd door de Unicode-standaard) en transformeert u deze in een reeks Unicode-tekens. Implementaties kunnen ervoor kiezen om aanvullende tekencoderingsschema's (zoals UTF-16, UTF-32 of niet-Unicode-tekentoewijzingen) te accepteren en te transformeren.
Opmerking: de verwerking van het Unicode NULL-teken (U+0000) is gedefinieerd door de implementatie. Het wordt sterk aanbevolen dat ontwikkelaars dit teken niet gebruiken in hun broncode, omwille van zowel draagbaarheid als leesbaarheid. Wanneer het teken is vereist binnen een letterlijk teken of tekenreeks, kunnen de escapereeksen
\0
worden gebruikt of\u0000
worden gebruikt. eindnotitie
Opmerking: het valt buiten het bereik van deze specificatie om te definiëren hoe een bestand met een andere tekenweergave dan Unicode kan worden omgezet in een reeks Unicode-tekens. Tijdens deze transformatie wordt echter aanbevolen dat het gebruikelijke regelscheidingsteken (of de reeks) in de andere tekenset wordt vertaald naar de reeks met twee tekens die bestaat uit het Unicode-teken voor regelterugloop (U+000D), gevolgd door Unicode-regelinvoerteken (U+000A). Voor het grootste deel heeft deze transformatie geen zichtbare effecten; Dit is echter van invloed op de interpretatie van letterlijke letterlijke tokens voor de letterlijke tekenreeks (§6.4.5.6). Het doel van deze aanbeveling is om een letterlijke letterlijke tekenreeks van een exacte tekenreeks te laten produceren wanneer de compilatie-eenheid wordt verplaatst tussen systemen die verschillende niet-Unicode-tekensets ondersteunen, met name tekensets die verschillende tekenreeksen gebruiken voor regelscheiding. eindnotitie
6.2 Grammatica's
6.2.1 Algemeen
Deze specificatie geeft de syntaxis van de C#-programmeertaal weer met behulp van twee grammaticas. De lexicale grammatica (§6.2.3) definieert hoe Unicode-tekens worden gecombineerd tot regeleindtekens, witruimte, opmerkingen, tokens en preverwerkingsrichtlijnen. De syntactische grammatica (§6.2.4) definieert hoe de tokens die voortvloeien uit de lexicale grammatica worden gecombineerd tot C#-programma's.
Alle terminaltekens moeten worden begrepen als het juiste Unicode-teken van het bereik U+0020 tot U+007F, in tegenstelling tot eventuele vergelijkbare tekens uit andere Unicode-tekenbereiken.
6.2.2 Grammatica notatie
De lexicale en syntactische grammatica worden weergegeven in de uitgebreide Backus-Naur-vorm van het ANTLR-grammaticaprogramma.
Hoewel de ANTLR-notatie wordt gebruikt, bevat deze specificatie geen volledige ANTLR-gereede "naslag grammatica" voor C#; het schrijven van een lexer en parser, hetzij handmatig of met behulp van een hulpprogramma zoals ANTLR, valt buiten het bereik van een taalspecificatie. Met deze kwalificatie probeert deze specificatie de kloof tussen de opgegeven grammatica en die nodig is voor het bouwen van een lexer en parser in ANTLR te minimaliseren.
ANTLR maakt onderscheid tussen lexicale en syntactische parser, aangeduid als ANTLR, grammatica's in de notatie door lexicale regels te starten met een hoofdletter en parserregels met een kleine letter.
Opmerking: De C#-lexicale grammatica (§6.2.3) en syntactische grammatica (§6.2.4) zijn niet exact in overeenstemming met de ANTLR-divisie in lexicale en parser grammers. Dit kleine verschil betekent dat sommige ANTLR-parserregels worden gebruikt bij het opgeven van de C#-lexicale grammatica. eindnotitie
6.2.3 Lexicale grammatica
De lexicale grammatica van C# wordt gepresenteerd in §6.3, §6.4 en §6.5. De terminalsymbolen van de lexicale grammatica zijn de tekens van de Unicode-tekenset en de lexicale grammatica geeft aan hoe tekens worden gecombineerd tot formuliertokens (§6.4), witruimte (§6.3.4), opmerkingen (§6.3.3) en preverwerkingsrichtlijnen (§6.5).
Veel van de terminalsymbolen van de syntactische grammatica worden niet expliciet gedefinieerd als tokens in de lexicale grammatica. In plaats daarvan wordt gebruikgemaakt van het ANTLR-gedrag dat letterlijke tekenreeksen in de grammatica worden geëxtraheerd als impliciete lexicale tokens; Hierdoor kunnen trefwoorden, operators, enzovoort in de grammatica worden weergegeven door hun letterlijke weergave in plaats van een tokennaam.
Elke compilatie-eenheid in een C#-programma moet voldoen aan de invoerproductie van de lexicale grammatica (§6.3.1).
6.2.4 Syntactische grammatica
De syntactische grammatica van C# wordt gepresenteerd in de componenten, subclauses en bijlagen die volgen op deze subclause. De terminalsymbolen van de syntactische grammatica zijn de tokens die expliciet zijn gedefinieerd door de lexicale grammatica en impliciet door letterlijke tekenreeksen in de grammatica zelf (§6.2.3). De syntactische grammatica geeft aan hoe tokens worden gecombineerd tot C#-programma's.
Elke compilatie-eenheid in een C#-programma voldoet aan de compilation_unit productie (§14.2) van de syntactische grammatica.
6.2.5 Grammatica ambiguïteiten
De producties voor simple_name (§12.8.4) en member_access (§12.8.7) kunnen leiden tot dubbelzinnigheid in de grammatica voor expressies.
Voorbeeld: De instructie:
F(G<A, B>(7));
kan worden geïnterpreteerd als een aanroep naar
F
met twee argumenten,G < A
enB > (7)
. Het kan ook worden geïnterpreteerd als een aanroep naarF
één argument. Dit is een aanroep naar een algemene methodeG
met twee typeargumenten en één gewoon argument.eindvoorbeeld
Als een reeks tokens kan worden geparseerd (in context) als een simple_name (§12.8.4), member_access (§12.8.7) of pointer_member_access (§23.6.3) eindigt op een type_argument_list (§8.4.2), wordt het token direct na het afsluitende token onderzocht om te zien of het token is>
- Een van
( ) ] } : ; , . ? == != | ^ && || & [
; of - Een van de relationele operatoren
< <= >= is as
; of - Een contextueel trefwoord voor query's dat wordt weergegeven in een query-expressie; of
- In bepaalde contexten wordt de id beschouwd als een ondubbelzinnig token. Deze contexten zijn de plaats waar de volgorde van tokens die niet eenduidig zijn, direct wordt voorafgegaan door een van de trefwoorden
is
ofcase
, ofout
ontstaat tijdens het parseren van het eerste element van een tuple-letterlijke waarde (in welk geval de tokens worden voorafgegaan door(
of:
en de id wordt gevolgd door een,
) of een volgend element van een letterlijke tuple.
Als het volgende token deel uitmaakt van deze lijst of een id in een dergelijke context, wordt de type_argument_list bewaard als onderdeel van de simple_name, member_access of pointer_member-toegang en een andere mogelijke parsering van de reeks tokens wordt verwijderd. Anders wordt de type_argument_list niet beschouwd als onderdeel van de simple_name, member_access of pointer_member_access, zelfs als er geen andere mogelijke parsering van de reeks tokens is.
Opmerking: deze regels worden niet toegepast bij het parseren van een type_argument_list in een namespace_or_type_name (§7.8). eindnotitie
Voorbeeld: De instructie:
F(G<A, B>(7));
wordt volgens deze regel geïnterpreteerd als een aanroep naar
F
één argument. Dit is een aanroep naar een algemene methodeG
met twee typeargumenten en één gewoon argument. De instructiesF(G<A, B>7); F(G<A, B>>7);
wordt elk geïnterpreteerd als een aanroep naar
F
met twee argumenten. De instructiex = F<A> + y;
wordt geïnterpreteerd als een operator die kleiner is dan, groter dan en een unaire-plus-operator, alsof de instructie is geschreven
x = (F < A) > (+y)
, in plaats van als een simple_name met een type_argument_list gevolgd door een binaire plus-operator. In de instructiex = y is C<T> && z;
de tokens
C<T>
worden geïnterpreteerd als een namespace_or_type_name met een type_argument_list vanwege de aanwezigheid van het ondubbelzinnige token&&
na de type_argument_list.De expressie
(A < B, C > D)
is een tuple met twee elementen, elk een vergelijking.De expressie
(A<B,C> D, E)
is een tuple met twee elementen, waarvan de eerste een declaratie-expressie is.De aanroep
M(A < B, C > D, E)
heeft drie argumenten.De aanroep
M(out A<B,C> D, E)
heeft twee argumenten, waarvan de eerste eenout
verklaring is.De expressie
e is A<B> C
maakt gebruik van een declaratiepatroon.Het caselabel
case A<B> C:
maakt gebruik van een declaratiepatroon.eindvoorbeeld
Bij de erkenning van een relational_expression (§12.12.1is
6.3 Lexicale analyse
6.3.1 Algemeen
Voor het gemak definieert en verwijst de lexicale grammatica naar de volgende benoemde lexertokens:
DEFAULT : 'default' ;
NULL : 'null' ;
TRUE : 'true' ;
FALSE : 'false' ;
ASTERISK : '*' ;
SLASH : '/' ;
Hoewel dit lexerregels zijn, worden deze namen in hoofdletters gespeld om ze te onderscheiden van gewone lexerregelnamen.
Opmerking: deze gemaksregels zijn uitzonderingen op de gebruikelijke procedure voor het niet verstrekken van expliciete tokennamen voor tokens die zijn gedefinieerd door letterlijke tekenreeksen. eindnotitie
De invoerproductie definieert de lexicale structuur van een C#-compilatie-eenheid.
input
: input_section?
;
input_section
: input_section_part+
;
input_section_part
: input_element* New_Line
| PP_Directive
;
input_element
: Whitespace
| Comment
| token
;
Opmerking: De bovenstaande grammatica wordt beschreven door ANTLR-parseringsregels, het definieert de lexicale structuur van een C#-compilatie-eenheid en niet lexicale tokens. eindnotitie
Vijf basiselementen vormen de lexicale structuur van een C#-compilatie-eenheid: Lijneindtekens (§6.3.2), witruimte (§6.3.4), opmerkingen (§6.3.3), tokens (§6.4) en voorverwerkingsrichtlijnen (§6.5). Van deze basiselementen zijn alleen tokens significant in de syntactische grammatica van een C#-programma (§6.2.4).
De lexicale verwerking van een C#-compilatie-eenheid bestaat uit het verminderen van het bestand in een reeks tokens die de invoer voor de syntactische analyse wordt. Regeleindtekens, witruimte en opmerkingen kunnen dienen om tokens te scheiden en richtlijnen voor preverwerking kunnen ertoe leiden dat secties van de compilatie-eenheid worden overgeslagen, maar anders hebben deze lexicale elementen geen invloed op de syntactische structuur van een C#-programma.
Wanneer verschillende lexicale grammaticaproducties overeenkomen met een reeks tekens in een compilatie-eenheid, vormt de lexicale verwerking altijd het langst mogelijke lexicale element.
Voorbeeld: De tekenreeks
//
wordt verwerkt als het begin van een opmerking met één regel, omdat dat lexicale element langer is dan één/
token. eindvoorbeeld
Sommige tokens worden gedefinieerd door een set lexicale regels; een hoofdregel en een of meer subregels. De laatste worden gemarkeerd in de grammatica door fragment
aan te geven dat de regel een deel van een ander token definieert. Fragmentregels worden niet beschouwd in de volgorde van boven naar beneden van lexicale regels.
Opmerking: In ANTLR
fragment
is een trefwoord dat hetzelfde gedrag produceert dat hier is gedefinieerd. eindnotitie
6.3.2 Regeleindtekens
Regeleindtekens verdelen de tekens van een C#-compilatie-eenheid in regels.
New_Line
: New_Line_Character
| '\u000D\u000A' // carriage return, line feed
;
Voor compatibiliteit met hulpprogramma's voor het bewerken van broncode die einde-of-bestandmarkeringen toevoegen en een compilatie-eenheid kan worden weergegeven als een reeks correct beëindigde regels, worden de volgende transformaties toegepast op elke compilatie-eenheid in een C#-programma:
- Als het laatste teken van de compilatie-eenheid een Control-Z-teken (U+001A) is, wordt dit teken verwijderd.
- Een teken voor regelterugloop (U+000D) wordt toegevoegd aan het einde van de compilatie-eenheid als die compilatie-eenheid niet leeg is en als het laatste teken van de compilatie-eenheid geen regelterugloop is (U+000D), een regelinvoer (U+000A), een volgend regelteken (U+0085), een regelscheidingsteken (U+2028) of een alineascheidingsteken (U+2029).
Opmerking: Met de extra regelterugloop kan een programma eindigen in een PP_Directive (§6.5) die geen afsluit-New_Line heeft. eindnotitie
6.3.3 Opmerkingen
Twee vormen van opmerkingen worden ondersteund: gescheiden opmerkingen en opmerkingen met één regel.
Een gescheiden opmerking begint met de tekens /*
en eindigt met de tekens */
. Opmerkingen met scheidingstekens kunnen een deel van een regel, één regel of meerdere regels in beslag nemen.
Voorbeeld: Het voorbeeld
/* Hello, world program This program writes "hello, world" to the console */ class Hello { static void Main() { System.Console.WriteLine("hello, world"); } }
bevat een opmerking met scheidingstekens.
eindvoorbeeld
Een opmerking met één regel begint met de tekens //
en breidt zich uit tot het einde van de regel.
Voorbeeld: Het voorbeeld
// 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"); } }
toont verschillende opmerkingen met één regel.
eindvoorbeeld
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
;
Opmerkingen worden niet genest. De tekenreeksen /*
en */
hebben geen speciale betekenis binnen een eenregelig commentaar. De tekenreeksen //
en /*
hebben geen speciale betekenis binnen een commentaar met scheidingstekens.
Opmerkingen worden niet verwerkt binnen letterlijke tekens en tekenreeksen.
Opmerking: Deze regels moeten zorgvuldig worden geïnterpreteerd. Bijvoorbeeld, in het onderstaande voorbeeld, de gescheiden opmerking die begint voordat
A
eindigt tussenB
enC()
. De reden hiervoor is dat// B */ C();
is eigenlijk geen opmerking met één regel, omdat
//
heeft geen speciale betekenis binnen een gescheiden opmerking, en dus*/
heeft de gebruikelijke speciale betekenis op die regel.Op dezelfde manier begint de gescheiden opmerking voordat
D
deze eindigt.E
De reden hiervoor is dat"D */ "
het niet echt een letterlijke tekenreeks is, omdat het eerste dubbele aanhalingsteken in een begrensde opmerking wordt weergegeven.Een nuttig gevolg van
/*
en*/
geen speciale betekenis binnen een opmerking met één regel is dat een blok broncoderegels kan worden uitgecommentareerd door//
aan het begin van elke regel te plaatsen. Over het algemeen werkt het niet om vóór deze regels en/*
na deze regels te plaatsen*/
, omdat dit niet goed gescheiden opmerkingen in het blok inkapselt en in het algemeen de structuur van dergelijke gescheiden opmerkingen volledig kan wijzigen.Voorbeeldcode:
static void Main() { /* A // B */ C(); Console.WriteLine(/* "D */ "E"); }
eindnotitie
Single_Line_Comment s en Delimited_Commentbepaalde indelingen hebben, kunnen worden gebruikt als documentatieopmerkingen, zoals beschreven in §D.
6.3.4 Witruimte
Witruimte wordt gedefinieerd als elk teken met Unicode-klasse Zs (inclusief het spatieteken) en het horizontale tabteken, het verticale tabteken en het formulierfeedteken.
Whitespace
: [\p{Zs}] // any character with Unicode class Zs
| '\u0009' // horizontal tab
| '\u000B' // vertical tab
| '\u000C' // form feed
;
6.4 Tokens
6.4.1 Algemeen
Er zijn verschillende soorten tokens: id's, trefwoorden, letterlijke waarden, operators en interpuncties. Witruimte en opmerkingen zijn geen tokens, hoewel ze fungeren als scheidingstekens voor tokens.
token
: identifier
| keyword
| Integer_Literal
| Real_Literal
| Character_Literal
| String_Literal
| operator_or_punctuator
;
Opmerking: Dit is een ANTLR-parserregel, het definieert geen lexical token, maar in plaats daarvan de verzameling tokentypen. eindnotitie
6.4.2 Escapereeksen voor Unicode-tekens
Een Unicode-escapereeks vertegenwoordigt een Unicode-codepunt. Unicode-escapereeksen worden verwerkt in id's (§6.4.3), letterlijke tekens (§6.4.5.5,5), letterlijke tekenreeksen (§6.4.5.6) en geïnterpoleerde reguliere tekenreeksexpressies (§12.8.3). Een Unicode-escapereeks wordt niet verwerkt op een andere locatie (bijvoorbeeld om een operator, interpunctie of trefwoord te vormen).
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
;
Een Escape-reeks Unicode-tekens vertegenwoordigt het enkele Unicode-codepunt dat wordt gevormd door het hexadecimale getal na de tekens \u of \U. Omdat C# gebruikmaakt van een 16-bits codering van Unicode-codepunten in teken- en tekenreekswaarden, wordt een Unicode-codepunt in het bereik U+10000
U+10FFFF
weergegeven met behulp van twee Unicode-surrogaatcode-eenheden. Unicode-codepunten hierboven U+FFFF
zijn niet toegestaan in letterlijke tekens. Unicode-codepunten hierboven U+10FFFF
zijn ongeldig en worden niet ondersteund.
Er worden niet meerdere vertalingen uitgevoerd. De letterlijke tekenreeks "\u005Cu005C"
is bijvoorbeeld gelijk aan "\u005C"
in plaats "\"
van .
Opmerking: de Unicode-waarde
\u005C
is het teken '\
'. eindnotitie
Voorbeeld: Het voorbeeld
class Class1 { static void Test(bool \u0066) { char c = '\u0066'; if (\u0066) { System.Console.WriteLine(c.ToString()); } } }
toont verschillende toepassingen van
\u0066
, de escape-reeks voor de letter "f
". Het programma is gelijk aanclass Class1 { static void Test(bool f) { char c = 'f'; if (f) { System.Console.WriteLine(c.ToString()); } } }
eindvoorbeeld
6.4.3 Id's
De regels voor id's in deze subclause komen exact overeen met de regels die worden aanbevolen door de Unicode-standaardbijlage 15, behalve dat onderstrepingsteken is toegestaan als een initiële teken (zoals traditioneel in de programmeertaal C), Unicode-escapereeksen zijn toegestaan in id's en het teken@
'' is toegestaan als voorvoegsel om trefwoorden als id's te kunnen gebruiken.
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
;
Opmerking:
- Zie De Unicode-standaard voor informatie over de Unicode-tekenklassen die hierboven worden genoemd.
- Het fragment Available_Identifier vereist de uitsluiting van trefwoorden en contextuele trefwoorden. Als de grammatica in deze specificatie wordt verwerkt met ANTLR, wordt deze uitsluiting automatisch verwerkt door de semantiek van ANTLR:
- Trefwoorden en contextuele trefwoorden vinden plaats in de grammatica als letterlijke tekenreeksen.
- ANTLR maakt impliciete lexicale tokenregels die worden gemaakt op basis van deze letterlijke tekenreeksen.
- ANTLR beschouwt deze impliciete regels vóór de expliciete lexicale regels in de grammatica.
- Fragment Available_Identifier komt daarom niet overeen met trefwoorden of contextuele trefwoorden als de lexicale regels voor die ervoor.
- Fragment Escaped_Identifier bevat escape-trefwoorden en contextuele trefwoorden omdat ze deel uitmaken van het langere token dat begint met een
@
en lexicale verwerking, vormt altijd het langst mogelijke lexicale element (§6.3.1).- Hoe een implementatie de beperkingen voor de toegestane Unicode_Escape_Sequence waarden afdwingt, is een implementatieprobleem.
eindnotitie
Voorbeeld: Voorbeelden van geldige id's zijn
identifier1
,_identifier2
en@if
. eindvoorbeeld
Een id in een conform programma moet de canonieke indeling hebben die is gedefinieerd door Unicode Normalization Form C, zoals gedefinieerd in Unicode Standard Bijlage 15. Het gedrag bij het tegenkomen van een id die zich niet in normalisatieformulier C bevindt, is door de implementatie gedefinieerd; er is echter geen diagnose vereist.
Het voorvoegsel '@
' maakt het gebruik van trefwoorden als id's mogelijk, wat handig is bij het faceren met andere programmeertalen. Het teken @
maakt geen deel uit van de id, dus de id kan in andere talen worden gezien als een normale id, zonder het voorvoegsel. Een id met een @
voorvoegsel wordt een verbatim-id genoemd.
Opmerking: Het gebruik van het
@
voorvoegsel voor id's die geen trefwoorden zijn, is toegestaan, maar sterk afgeraden als stijl. eindnotitie
Voorbeeld: Het voorbeeld:
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); } }
definieert een klasse met de naam '
class
' met een statische methode met de naam 'static
' die een parameter met de naam 'bool
' gebruikt. Omdat Unicode-escapes niet zijn toegestaan in trefwoorden, is het token 'cl\u0061ss
' een id en is het dezelfde id als '@class
'.eindvoorbeeld
Twee id's worden als identiek beschouwd als ze identiek zijn nadat de volgende transformaties zijn toegepast, in volgorde:
- Het voorvoegsel "
@
", indien gebruikt, wordt verwijderd. - Elke Unicode_Escape_Sequence wordt omgezet in het bijbehorende Unicode-teken.
- Alle Formatting_Characterworden verwijderd.
De semantiek van een benoemde _
id is afhankelijk van de context waarin deze wordt weergegeven:
- Het kan een benoemd programma-element, zoals een variabele, klasse of methode, of
- Het kan duiden op een verwijdering (§9.2.9.2).
Id's met twee opeenvolgende onderstrepingstekens (U+005F
) zijn gereserveerd voor gebruik door de implementatie. Er is echter geen diagnose vereist als een dergelijke id is gedefinieerd.
Opmerking: Een implementatie kan bijvoorbeeld uitgebreide trefwoorden bieden die beginnen met twee onderstrepingstekens. eindnotitie
6.4.4 Trefwoorden
Een trefwoord is een id-achtige reeks tekens die is gereserveerd en kan niet worden gebruikt als een id, behalve wanneer het @
teken vooraf wordt gegaan.
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'
;
Een contextueel trefwoord is een id-achtige reeks tekens met speciale betekenis in bepaalde contexten, maar is niet gereserveerd en kan worden gebruikt als een id buiten die contexten en wanneer het teken vooraf @
wordt gegaan.
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'
;
Opmerking: het trefwoord voor regels en contextual_keyword zijn parserregels omdat er geen nieuwe tokentypen worden geïntroduceerd. Alle trefwoorden en contextuele trefwoorden worden gedefinieerd door impliciete lexicale regels als letterlijke tekenreeksen in de grammatica (§6.2.3). eindnotitie
In de meeste gevallen is de syntactische locatie van contextuele trefwoorden zodanig dat ze nooit kunnen worden verward met gewoon id-gebruik. In een eigenschapsdeclaratie hebben de get
en set
id's bijvoorbeeld een speciale betekenis (§15.7.3). Een andere id dan get
of set
is nooit toegestaan op deze locaties, dus dit gebruik conflicteert niet met een gebruik van deze woorden als id's.
In bepaalde gevallen is de grammatica niet voldoende om contextueel trefwoordgebruik te onderscheiden van id's. In dergelijke gevallen wordt aangegeven hoe onderscheid moet worden tussen de twee. Het contextuele trefwoord var
in impliciet getypte declaraties van lokale variabelen (§13.6.2) kan bijvoorbeeld conflicteren met een gedeclareerd type genaamd var
. In dat geval heeft de gedeclareerde naam voorrang op het gebruik van de id als contextueel trefwoord.
Een ander voorbeeld van een dergelijke ondubbelzinnigheid is het contextuele trefwoord await
(§12.9.8.1), dat wordt beschouwd als een trefwoord alleen als binnen een methode gedeclareerd async
, maar kan worden gebruikt als een id elders.
Net als bij trefwoorden kunnen contextuele trefwoorden worden gebruikt als gewone id's door ze met het @
teken te voorzien.
Opmerking: wanneer deze id's worden gebruikt als contextuele trefwoorden, kunnen deze id's niet Unicode_Escape_Sequencebevatten. eindnotitie
6.4.5 Letterlijke tekens
6.4.5.1 Algemeen
Een letterlijke waarde (§12.8.2) is een broncodeweergave van een waarde.
literal
: boolean_literal
| Integer_Literal
| Real_Literal
| Character_Literal
| String_Literal
| null_literal
;
Opmerking: letterlijk is een parserregel omdat deze andere tokentypen groepeert en geen nieuw tokentype introduceert. eindnotitie
6.4.5.2 Booleaanse letterlijke waarden
Er zijn twee Booleaanse letterlijke waarden: true
en false
.
boolean_literal
: TRUE
| FALSE
;
Opmerking: boolean_literal is een parserregel omdat deze andere tokentypen groepeert en geen nieuw tokentype introduceert. eindnotitie
Het type van een boolean_literal is bool
.
6.4.5.3 Letterlijke gehele getallen
Letterlijke waarden voor gehele getallen worden gebruikt voor het schrijven van waarden van typenint
, uint
en long
ulong
. Letterlijke waarden voor gehele getallen hebben drie mogelijke vormen: decimaal, hexadecimaal en binair.
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'
;
Het type letterlijke geheel getal wordt als volgt bepaald:
- Als de letterlijke waarde geen achtervoegsel heeft, heeft deze de eerste van deze typen waarin de waarde kan worden weergegeven:
int
,uint
,long
.ulong
- Als de letterlijke waarde wordt achtervoegsel door
U
ofu
, heeft het de eerste van deze typen waarin de waarde ervan kan worden weergegeven:uint
,ulong
. - Als de letterlijke waarde wordt achtervoegsel door
L
ofl
, heeft het de eerste van deze typen waarin de waarde ervan kan worden weergegeven:long
,ulong
. - Als de letterlijke tekst wordt achtervoegsel door , , , , ,
UL
Ul
,uL
oful
, is het van het typeLU
.Lu
lU
lu
ulong
Als de waarde die wordt vertegenwoordigd door een letterlijk geheel getal buiten het bereik van het ulong
type valt, treedt er een compilatietijdfout op.
Opmerking: Als stijl wordt aanbevolen dat '
L
' wordt gebruikt in plaats van 'l
' bij het schrijven van letterlijke waarden van het typelong
, omdat het gemakkelijk is om de letter 'l
' te verwarren met het cijfer '1
'. eindnotitie
De volgende twee regels bestaan om de kleinste mogelijke int
waarden long
te laten schrijven als letterlijke gehele getallen:
- Wanneer een
−2147483648
2¹¹). In alle andere situaties is een dergelijke Integer_Literal van het typeuint
. - Wanneer een Integer_Literal de waarde
9223372036854775808
(2⁶³) en geen Integer_Type_Suffix of het Integer_Type_SuffixL
ofl
wordt weergegeven als het token direct na een unaire mintekentoken (§12.9.3), is het resultaat (van beide tokens) een constante van het typelong
met de waarde−9223372036854775808
(−2⁶³). In alle andere situaties is een dergelijke Integer_Literal van het typeulong
.
Voorbeeld:
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
eindvoorbeeld
6.4.5.4 Echte letterlijke gegevens
Echte letterlijke waarden worden gebruikt voor het schrijven van waarden van typen float
, double
en 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'
;
Als er geen Real_Type_Suffix is opgegeven, is het type double
. Anders bepaalt de Real_Type_Suffix het type van de werkelijke letterlijke tekst als volgt:
- Een echt letterlijk achtervoegsel dat door
F
off
van het typefloat
is.Voorbeeld: De letterlijke waarden
1f
,1.5f
en1e10f
123.456F
zijn allemaal van het typefloat
. eindvoorbeeld - Een echt letterlijk achtervoegsel dat door
D
ofd
van het typedouble
is.Voorbeeld: De letterlijke waarden
1d
,1.5d
en1e10d
123.456D
zijn allemaal van het typedouble
. eindvoorbeeld - Een echt letterlijk achtervoegsel dat door
M
ofm
van het typedecimal
is.Voorbeeld: De letterlijke waarden
1m
,1.5m
en1e10m
123.456M
zijn allemaal van het typedecimal
. eindvoorbeeld
Deze letterlijke waarde wordt geconverteerd naar eendecimal
waarde door de exacte waarde te nemen en, indien nodig, af te ronden op de dichtstbijzijnde vertegenwoordigbare waarde met behulp van de afronding van bankiers (§8.3.8). Elke schaal die zichtbaar is in de letterlijke waarde, blijft behouden, tenzij de waarde wordt afgerond. Opmerking: Vandaar dat de letterlijke2.900m
waarde wordt geparseerd om het tekendecimal
, de0
coëfficiënt2900
en de schaal3
te vormen. eindnotitie
Als de grootte van de opgegeven letterlijke waarde te groot is om te worden weergegeven in het aangegeven type, treedt er een compilatietijdfout op.
Opmerking: met name een Real_Literal zal nooit een drijvende-komma oneindigheid produceren. Een niet-nul-Real_Literal kan echter worden afgerond op nul. eindnotitie
De waarde van een echt letterlijke waarde van het type float
of double
wordt bepaald door de IEC 60559-modus 'round to nearest' te gebruiken met de bindingen die zijn verbroken tot 'even' (een waarde met de kleinste-significante-bits nul) en alle cijfers die als significant worden beschouwd.
Opmerking: In een echte letterlijke gegevens zijn decimalen altijd vereist na het decimaalteken. Is bijvoorbeeld
1.3F
een echte letterlijke1.F
maar niet. eindnotitieVoorbeeld:
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
eindvoorbeeld
Letterlijke tekens 6.4.5.5
Een letterlijk teken vertegenwoordigt één teken en bestaat uit een teken tussen aanhalingstekens, zoals in '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?
;
Opmerking: Een teken dat volgt op een backslash (
\
) in een teken, moet een van de volgende tekens zijn:'
, ,"
,\
0
a
,b
,f
n
,r
t
,u
, ,U
, ,x
.v
Anders treedt er een compilatietijdfout op. eindnotitie
Opmerking: Het gebruik van de
\x
Hexadecimal_Escape_Sequence productie kan foutgevoelig en moeilijk te lezen zijn vanwege het variabele aantal hexadecimale cijfers na de\x
. Bijvoorbeeld in de code:string good = "\x9Good text"; string bad = "\x9Bad text";
het kan in eerste instantie lijken dat het voorloopteken hetzelfde is (
U+0009
, een tabteken) in beide tekenreeksen. In feite begint de tweede tekenreeks alsU+9BAD
alle drie letters in het woord 'Slecht' geldige hexadecimale cijfers zijn. In stijl wordt aanbevolen om te voorkomen dat\x
specifieke escapereeksen (\t
in dit voorbeeld) of de escape-reeks met vaste lengte\u
worden vermeden.eindnotitie
Een hexadecimale escapereeks vertegenwoordigt één Unicode UTF-16-code-eenheid, met de waarde gevormd door het hexadecimale getal na "\x
".
Als de waarde die wordt vertegenwoordigd door een letterlijk teken groter is dan U+FFFF
, treedt er een compilatietijdfout op.
Een Unicode-escapereeks (§6.4.2) in een letterlijk teken moet zich in het bereik U+0000
tot U+FFFF
.
Een eenvoudige escapereeks vertegenwoordigt een Unicode-teken, zoals beschreven in de onderstaande tabel.
Escape-reeks | Tekennaam | Unicode-codepunt |
---|---|---|
\' |
Enkele aanhalingsteken | U+0027 |
\" |
Dubbele aanhalingsteken | U+0022 |
\\ |
Backslash | U+005C |
\0 |
Null | U+0000 |
\a |
Waarschuwing | U+0007 |
\b |
Backspace | U+0008 |
\f |
Formulierfeed | U+000C |
\n |
Nieuwe regel | U+000A |
\r |
Regelterugloop | U+000D |
\t |
Horizontaal tabblad | U+0009 |
\v |
Verticaal tabblad | U+000B |
Het type van een Character_Literal is char
.
6.4.5.6 Letterlijke tekenreeks
C# ondersteunt twee vormen van letterlijke tekenreeksen: gewone letterlijke tekenreeksen en letterlijke letterlijke tekenreeksen. Een gewone letterlijke tekenreeks bestaat uit nul of meer tekens tussen dubbele aanhalingstekens, zoals in "hello"
, en kan zowel eenvoudige escapereeksen (zoals \t
voor het tabteken) als hexadecimale en Unicode-escapereeksen bevatten.
Een letterlijke letterlijke verbatimtekenreeks bestaat uit een @
teken gevolgd door een dubbel aanhalingsteken, nul of meer tekens en een sluitend dubbel aanhalingsteken.
Voorbeeld: Een eenvoudig voorbeeld is
@"hello"
. eindvoorbeeld
In een letterlijke letterlijke verbatimtekenreeks worden de tekens tussen de scheidingstekens geïnterpreteerd, met de enige uitzondering als een Quote_Escape_Sequence, die één dubbel aanhalingsteken vertegenwoordigt. In het bijzonder worden eenvoudige escapereeksen en hexadecimale en Unicode-escapereeksen niet verwerkt in letterlijke letterlijke tekenreeksen. Een letterlijke letterlijke verbatimtekenreeks kan meerdere regels omvatten.
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
: '""'
;
Voorbeeld: Het voorbeeld
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";
toont een verscheidenheid aan letterlijke tekenreeksen. De laatste letterlijke tekenreeks,
j
is een letterlijke tekenreeks die meerdere regels omvat. De tekens tussen de aanhalingstekens, inclusief witruimte, zoals nieuwe regeltekens, blijven letterlijk behouden en elk paar dubbele aanhalingstekens wordt vervangen door een dergelijk teken.eindvoorbeeld
Opmerking: regeleinden binnen letterlijke letterlijke tekenreeksen maken deel uit van de resulterende tekenreeks. Als de exacte tekens die worden gebruikt om regeleinden te vormen, semantisch relevant zijn voor een toepassing, veranderen alle hulpprogramma's die regeleinden in broncode omzetten in verschillende indelingen (bijvoorbeeld tussen '
\n
' en '\r\n
') het gedrag van de toepassing. Ontwikkelaars moeten in dergelijke situaties voorzichtig zijn. eindnotitie
Opmerking: Omdat een hexadecimale escapereeks een variabel aantal hexe cijfers kan hebben, bevat de letterlijke tekenreeks
"\x123"
één teken met een hexwaarde123
. Als u een tekenreeks wilt maken met het teken met de hexwaarde12
gevolgd door het teken3
, kunt u schrijven"\x00123"
of"\x12"
+"3"
in plaats daarvan. eindnotitie
Het type van een String_Literal is string
.
Elke letterlijke tekenreeks resulteert niet noodzakelijkerwijs in een nieuw tekenreeksexemplaren. Wanneer twee of meer letterlijke tekenreeksen die equivalent zijn volgens de operator voor gelijkheid van tekenreeksen (§12.12.8), in dezelfde assembly worden weergegeven, verwijzen deze letterlijke tekenreeksen naar hetzelfde tekenreeksexemplaren.
Voorbeeld: bijvoorbeeld de uitvoer die wordt geproduceerd door
class Test { static void Main() { object a = "hello"; object b = "hello"; System.Console.WriteLine(a == b); } }
is
True
omdat de twee letterlijke tekens verwijzen naar hetzelfde tekenreeksexemplaren.eindvoorbeeld
6.4.5.7 De letterlijke waarde null
null_literal
: NULL
;
Opmerking: null_literal is een parserregel omdat er geen nieuw tokentype wordt geïntroduceerd. eindnotitie
Een null_literal vertegenwoordigt een null
waarde. Het heeft geen type, maar kan worden geconverteerd naar een verwijzingstype of null-waardetype via een null-letterlijke conversie (§10.2.7).
6.4.6 Operatoren en interpuncties
Er zijn verschillende soorten operatoren en leestekens. Operators worden gebruikt in expressies om bewerkingen met een of meer operanden te beschrijven.
Voorbeeld: De expressie
a + b
gebruikt de+
operator om de twee operandena
toe te voegen enb
. eindvoorbeeld
Interpunctietekens zijn bedoeld voor groepering en scheiding.
operator_or_punctuator
: '{' | '}' | '[' | ']' | '(' | ')' | '.' | ',' | ':' | ';'
| '+' | '-' | ASTERISK | SLASH | '%' | '&' | '|' | '^' | '!' | '~'
| '=' | '<' | '>' | '?' | '??' | '::' | '++' | '--' | '&&' | '||'
| '->' | '==' | '!=' | '<=' | '>=' | '+=' | '-=' | '*=' | '/=' | '%='
| '&=' | '|=' | '^=' | '<<' | '<<=' | '=>'
;
right_shift
: '>' '>'
;
right_shift_assignment
: '>' '>='
;
Opmerking: right_shift en right_shift_assignment zijn parserregels omdat ze geen nieuw tokentype introduceren, maar een reeks van twee tokens vertegenwoordigen. De operator_or_punctuator regel bestaat alleen voor beschrijvende doeleinden en wordt niet elders in de grammatica gebruikt. eindnotitie
right_shift bestaat uit de twee tokens >
en >
. Op dezelfde manier bestaat right_shift_assignment uit de twee tokens >
en >=
. In tegenstelling tot andere producties in de syntactische grammatica zijn er geen tekens van een soort (zelfs witruimte) toegestaan tussen de twee tokens in elk van deze producties. Deze producties worden speciaal behandeld om de juiste verwerking van type_parameter_lists mogelijk te maken (§15.2.3).
Opmerking: vóór de toevoeging van generics aan C#
>>
en>>=
beide enkele tokens waren. De syntaxis voor generics gebruikt echter de<
en>
tekens om parameters en typeargumenten te scheiden. Het is vaak wenselijk om geneste samengestelde typen te gebruiken, zoalsList<Dictionary<string, int>>
. In plaats van dat de programmeur de>
en>
door een spatie moet scheiden, is de definitie van de twee operator_or_punctuators gewijzigd. eindnotitie
6.5 Preverwerkingsrichtlijnen
6.5.1 Algemeen
De preverwerkingsrichtlijnen bieden de mogelijkheid om secties van compilatie-eenheden voorwaardelijk over te slaan, fout- en waarschuwingsvoorwaarden te rapporteren, afzonderlijke regio's van broncode te beschrijven en de context in te stellen die nullable zijn.
Opmerking: de term 'preverwerkingsrichtlijnen' wordt alleen gebruikt voor consistentie met de programmeertalen C en C++. In C# is er geen afzonderlijke stap voorverwerking; preverwerkingsrichtlijnen worden verwerkt als onderdeel van de lexicale analysefase. eindnotitie
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
;
Opmerking:
- De grammatica vóór de processor definieert één lexical token
PP_Directive
dat wordt gebruikt voor alle preverwerkingsrichtlijnen. De semantiek van elk van de preverwerkingsrichtlijnen worden gedefinieerd in deze taalspecificatie, maar niet hoe ze moeten worden geïmplementeerd.- Het
PP_Start
fragment mag alleen aan het begin van een regel worden herkend, hetgetCharPositionInLine() == 0
lexical predicaat ANTLR hierboven suggereert een manier waarop dit kan worden bereikt en alleen informatief is, een implementatie kan een andere strategie gebruiken.eindnotitie
De volgende voorverwerkingsrichtlijnen zijn beschikbaar:
-
#define
en#undef
, die worden gebruikt om respectievelijk voorwaardelijke compilatiesymbolen (§6.5.4) te definiëren en ongedaan te maken. -
#if
,#elif
,#else
en#endif
, die worden gebruikt om voorwaardelijke secties van de broncode over te slaan (§6.5.5). -
#line
, dat wordt gebruikt om regelnummers te bepalen die worden verzonden voor fouten en waarschuwingen (§6.5.8). -
#error
, die wordt gebruikt voor het uitgeven van fouten (§6.5.6). -
#region
en#endregion
, die worden gebruikt om secties van de broncode expliciet te markeren (§6.5.7). -
#nullable
, die wordt gebruikt om de nullable context (§6.5.9) op te geven. -
#pragma
, dat wordt gebruikt om optionele contextuele informatie op te geven aan een compiler (§6.5.10).
Een preverwerkingsrichtlijn neemt altijd een afzonderlijke regel broncode in beslag en begint altijd met een #
teken en een voorverwerkingsrichtlijnnaam. Witruimte kan optreden vóór het #
teken en tussen het #
teken en de naam van de instructie.
Een bronregel met een #define
, #undef
, , #if
#elif
, #else
, #endif
, , , #line
, of #endregion
#nullable
instructie kan eindigen met een opmerking met één regel. Gescheiden opmerkingen (de /* */
stijl van opmerkingen) zijn niet toegestaan op bronregels die preverwerkingsrichtlijnen bevatten.
Preverwerkingsrichtlijnen maken geen deel uit van de syntactische grammatica van C#. Preverwerkingsrichtlijnen kunnen echter worden gebruikt om reeksen tokens op te nemen of uit te sluiten en kunnen op die manier de betekenis van een C#-programma beïnvloeden.
Voorbeeld: Wanneer het programma is gecompileerd
#define A #undef B class C { #if A void F() {} #else void G() {} #endif #if B void H() {} #else void I() {} #endif }
resulteert in exact dezelfde reeks tokens als het programma
class C { void F() {} void I() {} }
Dus, terwijl de twee programma's lexisch verschillend zijn, syntactisch, zijn ze identiek.
eindvoorbeeld
6.5.2 Voorwaardelijke compilatiesymbolen
De functionaliteit voor voorwaardelijke compilatie die wordt geboden door de #if
, , en #elif
#else
richtlijnen wordt beheerd via preverwerkingsexpressies (#endif
) en voorwaardelijke compilatiesymbolen.
fragment PP_Conditional_Symbol
// Must not be equal to tokens TRUE or FALSE. See note below.
: Basic_Identifier
;
Opmerking Hoe een implementatie de beperking afdwingt voor de toegestane Basic_Identifier waarden is een implementatieprobleem. eindnotitie
Twee symbolen voor voorwaardelijke compilatie worden als identiek beschouwd als ze identiek zijn nadat de volgende transformaties zijn toegepast, in volgorde:
- Elke Unicode_Escape_Sequence wordt omgezet in het bijbehorende Unicode-teken.
- Alle Formatting_Characters worden verwijderd.
Een symbool voor voorwaardelijke compilatie heeft twee mogelijke statussen: gedefinieerd of niet gedefinieerd. Aan het begin van de lexicale verwerking van een compilatie-eenheid is een symbool voor voorwaardelijke compilatie niet gedefinieerd, tenzij het expliciet is gedefinieerd door een extern mechanisme (zoals een opdrachtregelcompilatoroptie). Wanneer een #define
richtlijn wordt verwerkt, wordt het symbool voor voorwaardelijke compilatie dat in die richtlijn wordt genoemd, gedefinieerd in die compilatie-eenheid. Het symbool blijft gedefinieerd totdat een #undef
instructie voor hetzelfde symbool wordt verwerkt of tot het einde van de compilatie-eenheid is bereikt. Een implicatie hiervan is dat #define
en #undef
instructies in één compilatie-eenheid geen effect hebben op andere compilatie-eenheden in hetzelfde programma.
Wanneer wordt verwezen in een voorverwerkingsexpressie (§6.5.3), heeft een gedefinieerd symbool voor voorwaardelijke compilatie de Booleaanse waarde true
en heeft een niet-gedefinieerd symbool voor voorwaardelijke compilatie de Booleaanse waarde false
. Er is geen vereiste dat voorwaardelijke compilatiesymbolen expliciet worden gedeclareerd voordat ze in preverwerkingsexpressies worden verwezen. In plaats daarvan zijn niet-declaratiede symbolen simpelweg niet gedefinieerd en hebben ze dus de waarde false
.
De naamruimte voor symbolen voor voorwaardelijke compilatie is uniek en gescheiden van alle andere benoemde entiteiten in een C#-programma. Voorwaardelijke compilatiesymbolen kunnen alleen worden verwezen in #define
en #undef
instructies en in preverwerkingsexpressies.
6.5.3 Expressies vóór verwerking
Preverwerkingsexpressies kunnen voorkomen in #if
en #elif
instructies. De operators !
(alleen voorvoegsel logische negatie), ==
, !=
, en &&
||
zijn toegestaan in voorverwerkingsexpressies en haakjes kunnen worden gebruikt voor groepering.
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? ')'
;
Wanneer naar een voorverwerkingsexpressie wordt verwezen, heeft een gedefinieerd symbool voor voorwaardelijke compilatie de Booleaanse waarde true
en heeft een niet-gedefinieerd symbool voor voorwaardelijke compilatie de Booleaanse waarde false
.
Evaluatie van een voorverwerkingsexpressie levert altijd een Booleaanse waarde op. De evaluatieregels voor een voorverwerkingsexpressie zijn hetzelfde als die voor een constante expressie (§12.23), behalve dat de enige door de gebruiker gedefinieerde entiteiten waarnaar kan worden verwezen, symbolen voor voorwaardelijke compilatie zijn.
6.5.4 Definitierichtlijnen
De definitie-instructies worden gebruikt voor het definiëren of ongedaan maken van voorwaardelijke compilatiesymbolen.
fragment PP_Declaration
: 'define' PP_Whitespace PP_Conditional_Symbol
| 'undef' PP_Whitespace PP_Conditional_Symbol
;
De verwerking van een #define
richtlijn zorgt ervoor dat het opgegeven symbool voor voorwaardelijke compilatie wordt gedefinieerd, te beginnen met de bronregel die volgt op de richtlijn. Evenzo zorgt de verwerking van een #undef
richtlijn ervoor dat het opgegeven symbool voor voorwaardelijke compilatie niet gedefinieerd wordt, te beginnen met de bronregel die volgt op de richtlijn.
Alle #define
instructies in een compilatie-eenheid vinden plaats vóór het eerste #undef
(§6.4) in de compilatie-eenheid; anders treedt er een compilatiefout op. In intuïtieve termen #define
en #undef
richtlijnen worden alle "echte code" in de compilatie-eenheid voorafgegaan.
Voorbeeld: Het voorbeeld:
#define Enterprise #if Professional || Enterprise #define Advanced #endif namespace Megacorp.Data { #if Advanced class PivotTable {...} #endif }
is geldig omdat de
#define
instructies voorafgaan aan het eerste token (hetnamespace
trefwoord) in de compilatie-eenheid.eindvoorbeeld
Voorbeeld: Het volgende voorbeeld resulteert in een compilatiefout omdat een #define de volgende echte code volgt:
#define A namespace N { #define B #if B class Class1 {} #endif }
eindvoorbeeld
Een #define
kan een voorwaardelijk compilatiesymbool definiëren dat al is gedefinieerd, zonder dat er tussenkomen #undef
voor dat symbool.
Voorbeeld: In het onderstaande voorbeeld wordt een voorwaardelijk compilatiesymbool A gedefinieerd en vervolgens opnieuw gedefinieerd.
#define A #define A
Voor compilers waarmee voorwaardelijke compilatiesymbolen kunnen worden gedefinieerd als compilatieopties, is het definiëren van het symbool als compileroptie en in de bron een alternatieve manier voor dergelijke herdefinities.
eindvoorbeeld
Een #undef
kan 'undefine' een symbool voor voorwaardelijke compilatie dat niet is gedefinieerd.
Voorbeeld: In het onderstaande voorbeeld wordt een symbool voor voorwaardelijke compilatie
A
gedefinieerd en vervolgens twee keer ongedaan gemaakt. Hoewel het tweede#undef
symbool geen effect heeft, is het nog steeds geldig.#define A #undef A #undef A
eindvoorbeeld
6.5.5 Richtlijnen voor voorwaardelijke compilatie
De richtlijnen voor voorwaardelijke compilatie worden gebruikt om delen van een compilatie-eenheid voorwaardelijk op te nemen of uit te sluiten.
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'
;
Voorwaardelijke compilatierichtlijnen worden geschreven in groepen die bestaan uit, in volgorde, een #if
richtlijn, nul of meer #elif
richtlijnen, nul of één #else
richtlijn, en een #endif
richtlijn. Tussen de richtlijnen bevinden zich voorwaardelijke secties van de broncode. Elke sectie wordt beheerd door de direct voorafgaande richtlijn. Een voorwaardelijke sectie kan zelf geneste richtlijnen voor voorwaardelijke compilatie bevatten, mits deze richtlijnen volledige groepen vormen.
Maximaal één van de ingesloten voorwaardelijke secties is geselecteerd voor normale lexicale verwerking:
- De PP_Expressionvan de
#if
en#elif
richtlijnen worden in volgorde geëvalueerd totdat één resultaat opleverttrue
. Als een expressie opleverttrue
, wordt de voorwaardelijke sectie na de bijbehorende instructie geselecteerd. - Als alle PP_Expressionrendement
false
en als er een#else
richtlijn aanwezig is, wordt de voorwaardelijke sectie na de#else
richtlijn geselecteerd. - Anders is er geen voorwaardelijke sectie geselecteerd.
De geselecteerde voorwaardelijke sectie, indien aanwezig, wordt verwerkt als een normale input_section: de broncode in de sectie houdt zich aan de lexicale grammatica; tokens worden gegenereerd op basis van de broncode in de sectie; en voorverwerkingsrichtlijnen in de sectie hebben de voorgeschreven effecten.
Alle resterende voorwaardelijke secties worden overgeslagen en er worden geen tokens, met uitzondering van voorverwerkingsrichtlijnen, gegenereerd op basis van de broncode. Daarom kan de broncode, met uitzondering van voorverwerkingsrichtlijnen, lexisch onjuist zijn. Overgeslagen voorverwerkingsrichtlijnen moeten lexisch correct zijn, maar worden anders niet verwerkt. In een voorwaardelijke sectie die wordt overgeslagen, worden ook geneste voorwaardelijke secties (in geneste #if...#endif
constructies) overgeslagen.
Opmerking: in de bovenstaande grammatica wordt niet vastgelegd dat de voorwaardelijke secties tussen de preverwerkingsrichtlijnen lexicaal onjuist zijn ingedeeld. Daarom is de grammatica niet gereed voor ANTLR, omdat deze alleen lexisch juiste invoer ondersteunt. eindnotitie
Voorbeeld: In het volgende voorbeeld ziet u hoe richtlijnen voor voorwaardelijke compilatie kunnen worden genest:
#define Debug // Debugging on #undef Trace // Tracing off class PurchaseTransaction { void Commit() { #if Debug CheckConsistency(); #if Trace WriteToLog(this.ToString()); #endif #endif CommitHelper(); } ... }
Met uitzondering van voorverwerkingsrichtlijnen is overgeslagen broncode niet onderworpen aan lexicale analyse. Het volgende is bijvoorbeeld geldig ondanks de onbepaalde opmerking in de
#else
sectie:#define Debug // Debugging on class PurchaseTransaction { void Commit() { #if Debug CheckConsistency(); #else /* Do something else #endif } ... }
Houd er echter rekening mee dat preverwerkingsrichtlijnen lexisch moeten worden gecorrigeerd, zelfs in overgeslagen secties van broncode.
Preverwerkingsrichtlijnen worden niet verwerkt wanneer ze in invoerelementen met meerdere regels worden weergegeven. Bijvoorbeeld het programma:
class Hello { static void Main() { System.Console.WriteLine(@"hello, #if Debug world #else Nebraska #endif "); } }
resulteert in de uitvoer:
hello, #if Debug world #else Nebraska #endif
In bijzondere gevallen kan de reeks voorverwerkingsrichtlijnen die worden verwerkt, afhankelijk zijn van de evaluatie van de pp_expression. Het voorbeeld:
#if X /* #else /* */ class Q { } #endif
produceert altijd dezelfde tokenstroom (
class
Q
{
}
), ongeacht of deze al dan nietX
is gedefinieerd. AlsX
deze is gedefinieerd, zijn#if
de enige verwerkte instructies en#endif
, vanwege de opmerking met meerdere regels. AlsX
dit niet is gedefinieerd, maken drie richtlijnen (#if
,#else
,#endif
) deel uit van de set met richtlijnen.eindvoorbeeld
6.5.6 Diagnostische instructies
De diagnostische instructies worden gebruikt om expliciet fout- en waarschuwingsberichten te genereren die op dezelfde manier worden gerapporteerd als andere compilatiefouten en waarschuwingen.
fragment PP_Diagnostic
: 'error' PP_Message?
| 'warning' PP_Message?
;
fragment PP_Message
: PP_Whitespace Input_Character*
;
Voorbeeld: Het voorbeeld
#if Debug && Retail #error A build can't be both debug and retail #endif class Test {...}
produceert een compilatiefout ('Een build kan niet zowel foutopsporing als detailhandel zijn) als de symbolen voor voorwaardelijke compilatie
Debug
enRetail
beide zijn gedefinieerd. Houd er rekening mee dat een PP_Message willekeurige tekst kan bevatten, met name omdat deze geen goed opgemaakte tokens hoeft te bevatten, zoals wordt weergegeven door de enkele aanhalingsteken in het woordcan't
.eindvoorbeeld
6.5.7 Regiorichtlijnen
De regio-instructies worden gebruikt om expliciet regio's van broncode te markeren.
fragment PP_Region
: PP_Start_Region
| PP_End_Region
;
fragment PP_Start_Region
: 'region' PP_Message?
;
fragment PP_End_Region
: 'endregion' PP_Message?
;
Er is geen semantische betekenis gekoppeld aan een regio; regio's zijn bedoeld voor gebruik door de programmeur of door geautomatiseerde hulpprogramma's om een sectie met broncode te markeren. Er is één richtlijn die #endregion
overeenkomt met elke #region
richtlijn. Het bericht dat in een #region
of #endregion
de richtlijn is opgegeven, heeft eveneens geen semantische betekenis. Het is slechts bedoeld om de regio te identificeren. Overeenkomsten #region
en #endregion
richtlijnen kunnen verschillende PP_Message hebben.
De lexicale verwerking van een regio:
#region
...
#endregion
komt exact overeen met de lexicale verwerking van een voorwaardelijke compilatierichtlijn van de vorm:
#if true
...
#endif
Opmerking: dit betekent dat een regio een of meer
#if
/.../#endif
, of kan worden opgenomen in een voorwaardelijke sectie binnen een#if
/.../#endif
; maar een regio mag niet overlappen met slechts een deel van een#if
/.../#endif
, of beginnen en eindigen in verschillende voorwaardelijke secties. eindnotitie
6.5.8 Regelrichtlijnen
Regelrichtlijnen kunnen worden gebruikt om de regelnummers en compilatie-eenheidsnamen te wijzigen die worden gerapporteerd door een compiler in uitvoer, zoals waarschuwingen en fouten. Deze waarden worden ook gebruikt door aanroeper-infokenmerken (§22.5.6).
Opmerking: Regelrichtlijnen worden meestal gebruikt in metaprogrammeerprogramma's die C#-broncode genereren op basis van andere tekstinvoer. eindnotitie
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' | '"')
;
Wanneer er geen #line
instructies aanwezig zijn, rapporteert een compiler echte regelnummers en namen van compilatie-eenheden in de uitvoer. Bij het verwerken van een #line
richtlijn die een PP_Line_Indicator bevat die niet default
, behandelt een compiler de regel na de richtlijn als het opgegeven regelnummer (en de naam van de compilatie-eenheid).
De maximaal toegestane Decimal_Digit+
waarde is door de implementatie gedefinieerd.
Een #line default
richtlijn doet het effect van alle voorgaande #line
richtlijnen ongedaan. Een compiler rapporteert echte regelinformatie voor volgende regels, precies alsof er geen #line
richtlijnen zijn verwerkt.
Een #line hidden
richtlijn heeft geen effect op de compilatie-eenheid en regelnummers die in foutberichten worden gerapporteerd of geproduceerd door gebruik van CallerLineNumberAttribute
(§22.5.6.2). Het is bedoeld om invloed te hebben op hulpprogramma's voor foutopsporing op bronniveau, zodat bij foutopsporing alle regels tussen een #line hidden
richtlijn en de volgende #line
richtlijn (dat wil niet #line hidden
) geen informatie over regelnummers hebben en volledig worden overgeslagen bij het doorlopen van code.
Opmerking: Hoewel een PP_Compilation_Unit_Name tekst kan bevatten die eruitziet als een escape-reeks, is deze tekst geen escape-reeks; in deze context wijst een '
\
'-teken gewoon een gewone backslash-teken aan. eindnotitie
6.5.9 Nullable directive
De null-instructie bepaalt de context die null kan worden gebruikt, zoals hieronder wordt beschreven.
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'
;
Met een null-instructie worden de beschikbare vlaggen ingesteld voor volgende regels code, totdat een andere null-instructie deze overschrijft of tot het einde van de compilatie-_unit is bereikt. De null-context bevat twee vlaggen: aantekeningen en waarschuwingen. Het effect van elke vorm van null-instructie is als volgt:
-
#nullable disable
: Schakelt zowel null-aantekeningen als null-waarschuwingenvlagmen uit. -
#nullable enable
: hiermee schakelt u zowel null-aantekeningen als null-waarschuwingenvlagmen in. -
#nullable restore
: herstelt zowel de aantekeningen als waarschuwingen naar de status die is opgegeven door het externe mechanisme, indien van toepassing. -
#nullable disable annotations
: hiermee schakelt u de vlag voor null-aantekeningen uit. De vlag voor null-waarschuwingen wordt niet beïnvloed. -
#nullable enable annotations
: hiermee schakelt u de vlag voor null-aantekeningen in. De vlag voor null-waarschuwingen wordt niet beïnvloed. -
#nullable restore annotations
: herstelt de vlag voor null-aantekeningen naar de status die is opgegeven door het externe mechanisme, indien van toepassing. De vlag voor null-waarschuwingen wordt niet beïnvloed. -
#nullable disable warnings
: Hiermee schakelt u de vlag voor null-waarschuwingen uit. De vlag voor null-aantekeningen wordt niet beïnvloed. -
#nullable enable warnings
: Hiermee schakelt u de vlag voor null-waarschuwingen in. De vlag voor null-aantekeningen wordt niet beïnvloed. -
#nullable restore warnings
: herstelt de vlag voor null-waarschuwingen naar de status die is opgegeven door het externe mechanisme, indien van toepassing. De vlag voor null-aantekeningen wordt niet beïnvloed.
De null-status van expressies wordt altijd bijgehouden. De status van de aantekeningsvlag en de aanwezigheid of afwezigheid van een null-aantekening, ?
bepaalt de initiële null-status van een variabeledeclaratie. Waarschuwingen worden alleen afgegeven wanneer de waarschuwingsvlag is ingeschakeld.
Voorbeeld: Het voorbeeld
#nullable disable string x = null; string y = ""; #nullable enable Console.WriteLine(x.Length); // Warning Console.WriteLine(y.Length);
produceert een waarschuwing over de compilatietijd ('zoals
x
isnull
'). De null-status vanx
overal wordt bijgehouden. Er wordt een waarschuwing weergegeven wanneer de waarschuwingsvlag is ingeschakeld.eindvoorbeeld
6.5.10 Pragma-richtlijnen
De #pragma
preverwerkingsrichtlijn wordt gebruikt om contextuele informatie aan een compiler op te geven.
Opmerking: een compiler kan bijvoorbeeld instructies geven
#pragma
die
- Bepaalde waarschuwingsberichten in- of uitschakelen bij het compileren van volgende code.
- Geef op welke optimalisaties moeten worden toegepast op volgende code.
- Geef informatie op die moet worden gebruikt door een foutopsporingsprogramma.
eindnotitie
fragment PP_Pragma
: 'pragma' PP_Pragma_Text?
;
fragment PP_Pragma_Text
: PP_Whitespace Input_Character*
;
De Input_Characters in de PP_Pragma_Text worden door een compiler op een implementatiegedefinieerde manier geïnterpreteerd. De in een #pragma
richtlijn verstrekte informatie mag de semantiek van het programma niet wijzigen. Een #pragma
richtlijn wijzigt alleen het compilergedrag dat buiten het bereik van deze taalspecificatie valt. Als een compiler de Input_Characters niet kan interpreteren, kan een compiler een waarschuwing produceren; Er wordt echter geen compilatiefout gegenereerd.
Opmerking: PP_Pragma_Text kan willekeurige tekst bevatten; in het bijzonder hoeft deze geen goed gevormde tokens te bevatten. eindnotitie
ECMA C# draft specification