Freigeben über


6 Lexikalische Struktur

6.1 Programme

Ein C#-Programm besteht aus einer oder mehreren Quelldateien, die formell als Kompilierungseinheiten (§14.2) bezeichnet werden. Obwohl eine Kompilierungseinheit möglicherweise eine 1:1-Entsprechung mit einer Datei in einem Dateisystem hat, ist diese Korrespondenz nicht erforderlich.

Konzeptionell wird ein Programm mit drei Schritten kompiliert:

  1. Transformation, die eine Datei aus einem bestimmten Zeichenrepertoire und Codierungsschema in eine Sequenz von Unicode-Zeichen konvertiert.
  2. Lexikalische Analyse, die einen Datenstrom von Unicode-Eingabezeichen in einen Datenstrom von Token übersetzt.
  3. Syntaktische Analyse, die den Datenstrom von Token in ausführbaren Code übersetzt.

Konforme Implementierungen akzeptieren Unicode-Kompilierungseinheiten, die mit dem UTF-8-Codierungsformular codiert sind (gemäß der Definition durch den Unicode-Standard), und transformieren sie in eine Sequenz von Unicode-Zeichen. Implementierungen können zusätzliche Zeichencodierungsschemas akzeptieren und transformieren (z. B. UTF-16, UTF-32 oder Nicht-Unicode-Zeichenzuordnungen).

Hinweis: Die Behandlung des Unicode-NULL-Zeichens (U+0000) ist implementierungsdefiniert. Es wird dringend empfohlen, dass Entwickler dieses Zeichen nicht in ihrem Quellcode verwenden, um portierbar und lesbar zu sein. Wenn das Zeichen innerhalb eines Zeichens oder Zeichenfolgenliterals erforderlich ist, werden die Escapesequenzen oder \u0000 stattdessen \0 verwendet. Endnote

Hinweis: Es liegt außerhalb des Gültigkeitsbereichs dieser Spezifikation, um zu definieren, wie eine Datei mit einer anderen Zeichendarstellung als Unicode in eine Sequenz von Unicode-Zeichen umgewandelt werden kann. Bei einer solchen Transformation wird jedoch empfohlen, dass das übliche Zeilentrennzeichen (oder die normale Sequenz) im anderen Zeichensatz in die zweistellige Sequenz übersetzt wird, die aus dem Unicode-Wagenrücklaufzeichen (U+000D) gefolgt von Unicode-Zeilenvorschubzeichen (U+000A) besteht. In den meisten Fällen hat diese Transformation keine sichtbaren Effekte; Sie wirkt sich jedoch auf die Interpretation von verbatim Zeichenfolgenliteraltoken (§6.4.5.6) aus. Mit dieser Empfehlung soll ein verbatimes Zeichenfolgenliteral dieselbe Zeichenfolge erzeugen, wenn die Kompilierungseinheit zwischen Systemen verschoben wird, die unterschiedliche Nicht-Unicode-Zeichensätze unterstützen, insbesondere solche, die unterschiedliche Zeichenabfolgen für die Zeilentrennung verwenden. Endnote

6.2 Grammatiken

6.2.1 Allgemein

Diese Spezifikation stellt die Syntax der C#-Programmiersprache mit zwei Grammatiken dar. Die lexikalische Grammatik (§6.2.3) definiert, wie Unicode-Zeichen kombiniert werden, um Zeilenterminatoren, Leerzeichen, Kommentare, Token und Vorverarbeitungsdirektiven zu bilden. Die syntaktische Grammatik (§6.2.4) definiert, wie die token aus der lexikalischen Grammatik kombiniert werden, um C#-Programme zu bilden.

Alle Terminalzeichen sind als geeignetes Unicode-Zeichen aus dem Bereich U+0020 bis U+007F zu verstehen, im Gegensatz zu ähnlich aussehenden Zeichen aus anderen Unicode-Zeichenbereichen.

6.2.2 Grammatiknotation

Die lexikalischen und syntaktischen Grammatiken werden in der Form "Extended Backus-Naur" des ANTLR-Grammatiktools vorgestellt.

Während die ANTLR-Notation verwendet wird, stellt diese Spezifikation keine vollständige ANTLR-bereite "Referenzgrammatik" für C# dar; Das Schreiben eines Lexikals und Parsers, entweder von Hand oder mithilfe eines Tools wie ANTLR, liegt außerhalb des Umfangs einer Sprachspezifikation. Mit dieser Qualifikation versucht diese Spezifikation, die Lücke zwischen der angegebenen Grammatik und der zum Erstellen eines Lexikals und Parsers in ANTLR erforderlichen Zuteilung zu minimieren.

ANTLR unterscheidet zwischen lexikalischen und syntaktischen, von ANTLR bezeichneten Parser, Grammatiken in ihrer Schreibweise, indem lexikalische Regeln mit einem Großbuchstaben und Parserregeln mit einem Kleinbuchstaben gestartet werden.

Hinweis: Die lexikalische C#-Grammatik (§6.2.3) und syntaktische Grammatik (§6.2.4) entsprechen nicht exakt der ANTLR-Division in lexikalische und Parser grammer. Diese kleine Übereinstimmung bedeutet, dass einige ANTLR-Parserregeln beim Angeben der lexikalischen C#-Grammatik verwendet werden. Endnote

6.2.3 Lexikalische Grammatik

Die lexikalische Grammatik von C# wird in §6.3, §6.4 und §6.5 dargestellt. Die Terminalsymbole der lexikalischen Grammatik sind die Zeichen des Unicode-Zeichensatzes, und die lexikalische Grammatik gibt an, wie Zeichen in Form von Token (§6.4), Leerzeichen (§6.3.4), Kommentare (§6.3.3) und Vorverarbeitungsdirektiven (§6.5) kombiniert werden.

Viele der Terminalsymbole der syntaktischen Grammatik werden nicht explizit als Token in der lexikalischen Grammatik definiert. Stattdessen wird das ANTLR-Verhalten genutzt, das literale Zeichenfolgen in der Grammatik als implizite lexikalische Token extrahiert werden; Auf diese Weise können Schlüsselwörter, Operatoren usw. in der Grammatik durch ihre Literaldarstellung anstelle eines Tokennamens dargestellt werden.

Jede Kompilierungseinheit in einem C#-Programm entspricht der Eingabeproduktion der lexikalischen Grammatik (§6.3.1).

6.2.4 Syntaktische Grammatik

Die syntaktische Grammatik von C# wird in den Klauseln, Unterclauses und Anhängen dargestellt, die diesem Unterclause folgen. Die Terminalsymbole der syntaktischen Grammatik sind die Token, die explizit durch die lexikalische Grammatik und implizit durch Literalzeichenfolgen in der Grammatik selbst definiert werden (§6.2.3). Die syntaktische Grammatik gibt an, wie Token kombiniert werden, um C#-Programme zu bilden.

Jede Kompilierungseinheit in einem C#-Programm entspricht der compilation_unit Produktion (§14.2) der syntaktischen Grammatik.

6.2.5 Grammatikdeutigkeiten

Die Produktionen für simple_name (§12.8.4) und member_access (§12.8.7) können zu Mehrdeutigkeiten in der Grammatik für Ausdrücke führen.

Beispiel: Die Anweisung:

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

kann als Aufruf mit F zwei Argumenten interpretiert werden und G < A B > (7). Alternativ kann sie als Aufruf F mit einem Argument interpretiert werden, bei dem es sich um einen Aufruf einer generischen Methode G mit zwei Typargumenten und einem regulären Argument handelt.

Endbeispiel

Wenn eine Abfolge von Token (im Kontext) als simple_name (§12.8.4), member_access (§12.8.7) oder pointer_member_access (§23.6.3) mit einem type_argument_list (§8.4.2) enden kann, wird das Token unmittelbar nach dem schließenden > Token untersucht, um festzustellen, ob es sich um einen type_argument_list (§8.4.2) handelt.

  • Einer von ( ) ] } : ; , . ? == != | ^ && || & [; oder
  • Einer der relationalen Operatoren < <= >= is as; oder
  • Ein Kontextabfrage-Schlüsselwort, das in einem Abfrageausdruck angezeigt wird; oder
  • In bestimmten Kontexten wird der Bezeichner als mehrdeutigendes Token behandelt. In diesen Kontexten wird die Abfolge von Mehrdeutigkeitstoken unmittelbar einem der Schlüsselwörter iscase outvorangestellt oder entsteht beim Analysieren des ersten Elements eines Tupelliterals (in diesem Fall stehen die Token vor ( oder : nach einem ,) oder einem nachfolgenden Element eines Tupelliterals.

Wenn das folgende Token zu dieser Liste oder einem Bezeichner in einem solchen Kontext gehört, wird die type_argument_list als Teil der simple_name, member_access oder pointer_member zugriff beibehalten, und jede andere mögliche Analyse der Tokensequenz wird verworfen. Andernfalls gilt die type_argument_list nicht als Teil der simple_name, member_access oder pointer_member_access, auch wenn es keine andere mögliche Analyse der Abfolge von Token gibt.

Hinweis: Diese Regeln werden beim Analysieren einer type_argument_list in einem namespace_or_type_name (§7.8) nicht angewendet. Endnote

Beispiel: Die Anweisung:

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

wird gemäß dieser Regel als Aufruf eines F Arguments interpretiert, bei dem es sich um einen Aufruf einer generischen Methode G mit zwei Typargumenten und einem regulären Argument handelt. Die Anweisungen

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

wird jeweils als Aufruf F mit zwei Argumenten interpretiert. Die -Anweisung

x = F<A> + y;

wird als Kleiner-als-Operator, größer als Operator und unary-plus-Operator interpretiert, als ob die Anweisung geschrieben x = (F < A) > (+y)wurde, anstatt als simple_name mit einem type_argument_list gefolgt von einem Binären Plus-Operator. In der Anweisung

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

die Token C<T> werden aufgrund des Vorhandenseins des mehrdeutigen Tokens && nach dem type_argument_list als namespace_or_type_name mit einer type_argument_list interpretiert.

Der Ausdruck (A < B, C > D) ist ein Tupel mit zwei Elementen, jeweils ein Vergleich.

Der Ausdruck (A<B,C> D, E) ist ein Tupel mit zwei Elementen, deren erstes ein Deklarationsausdruck ist.

Der Aufruf M(A < B, C > D, E) weist drei Argumente auf.

Der Aufruf M(out A<B,C> D, E) hat zwei Argumente, deren erstes eine out Deklaration ist.

Der Ausdruck e is A<B> C verwendet ein Deklarationsmuster.

Die Fallbezeichnung case A<B> C: verwendet ein Deklarationsmuster.

Endbeispiel

Wenn eine relational_expression (§12.12.1) erkannt wird, wenn sowohl die Alternativen "relational_expression Typ" als auch "relational_expression is is constant_pattern" anwendbar sind und der Typ in einen barrierefreien Typ aufgelöst wird, wird die Alternative "relational_expression is Typ" ausgewählt.

6.3 Lexikalische Analyse

6.3.1 Allgemein

Aus Gründen der Einfachheit definiert und verweist die lexikalische Grammatik auf die folgenden benannten Lexikaltoken:

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

Obwohl es sich hierbei um Lexikalregeln handelt, werden diese Namen in Großbuchstaben geschrieben, um sie von gewöhnlichen Lexerregelnamen zu unterscheiden.

Hinweis: Diese Komfortregeln sind Ausnahmen von der üblichen Praxis, explizite Tokennamen für Token bereitzustellen, die durch Literalzeichenfolgen definiert sind. Endnote

Die Eingabeproduktion definiert die lexikalische Struktur einer C#-Kompilierungseinheit.

input
    : input_section?
    ;

input_section
    : input_section_part+
    ;

input_section_part
    : input_element* New_Line
    | PP_Directive
    ;

input_element
    : Whitespace
    | Comment
    | token
    ;

Hinweis: Die oben genannte Grammatik wird durch ANTLR-Analyseregeln beschrieben, sie definiert die lexikalische Struktur einer C#-Kompilierungseinheit und nicht lexikalische Token. Endnote

Fünf grundlegende Elemente bilden die lexikalische Struktur einer C#-Kompilierungseinheit: Linienterminatoren (§6.3.2), Leerraum (§6.3.4), Kommentare (§6.3.3), Token (§6.4) und Vorverarbeitungsdirektiven (§6.5). Von diesen grundlegenden Elementen sind nur Token in der syntaktischen Grammatik eines C#-Programms (§6.2.4) von Bedeutung.

Die lexikalische Verarbeitung einer C#-Kompilierungseinheit besteht darin, die Datei in eine Abfolge von Token zu reduzieren, die zur Eingabe in die syntaktische Analyse werden. Zeilenterminatoren, Leerzeichen und Kommentare können zum Trennen von Token dienen, und Vorverarbeitungsdirektiven können dazu führen, dass Abschnitte der Kompilierungseinheit übersprungen werden, andernfalls haben diese lexikalischen Elemente keine Auswirkungen auf die syntaktische Struktur eines C#-Programms.

Wenn mehrere lexikalische Grammatikproduktionen mit einer Abfolge von Zeichen in einer Kompilierungseinheit übereinstimmen, bildet die lexikalische Verarbeitung immer das längste lexikalische Element.

Beispiel: Die Zeichensequenz // wird als Anfang eines einzelnen Zeilenkommentar verarbeitet, da dieses lexikalische Element länger als ein einzelnes / Token ist. Endbeispiel

Einige Token werden durch eine Reihe lexikalischer Regeln definiert; eine Hauptregel und eine oder mehrere Unterregeln. Letztere werden in der Grammatik markiert, indem fragment angegeben wird, dass die Regel einen Teil eines anderen Tokens definiert. Fragmentregeln werden nicht in der obersten bis unteren Reihenfolge der lexikalischen Regeln berücksichtigt.

Hinweis: In ANTLR fragment ist ein Schlüsselwort, das dasselbe hier definierte Verhalten erzeugt. Endnote

6.3.2 Zeilenabschlusszeichen

Zeilentrennzeichen teilen die Zeichen einer C#-Kompilierungseinheit in Zeilen auf.

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

Aus Gründen der Kompatibilität mit Quellcodebearbeitungstools, die End-of-File-Markierungen hinzufügen und eine Kompilierungseinheit als Sequenz ordnungsgemäß beendeter Zeilen anzeigen können, werden die folgenden Transformationen auf jede Kompilierungseinheit in einem C#-Programm angewendet:

  • Wenn das letzte Zeichen der Kompilierungseinheit ein Control-Z-Zeichen (U+001A) ist, wird dieses Zeichen gelöscht.
  • Am Ende der Kompilierungseinheit wird ein Wagenrücklaufzeichen (U+000D) hinzugefügt, wenn diese Kompilierungseinheit nicht leer ist und das letzte Zeichen der Kompilierungseinheit kein Wagenrücklauf (U+000D), ein Zeilenvorschub (U+000A), ein nächstes Zeilenzeichen (U+0085), ein Zeilentrennzeichen (U+2028) oder ein Absatztrennzeichen (U+2029) ist.

Hinweis: Mit der zusätzlichen Wagenrücklauf kann ein Programm in einem PP_Directive (§6.5) enden, der keine beendigungsfreie New_Line hat. Endnote

6.3.3 Kommentare

Es werden zwei Arten von Kommentaren unterstützt: durch Trennzeichen getrennte Kommentare und Einzel-Zeilenkommentar.

Ein durch Trennzeichen getrennter Kommentar beginnt mit den Zeichen /* und endet mit den Zeichen */. Durch Trennzeichen getrennte Kommentare können einen Teil einer Zeile, einer einzelnen Zeile oder mehrerer Zeilen belegen.

Beispiel: Das Beispiel

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

enthält einen durch Trennzeichen getrennten Kommentar.

Endbeispiel

Ein einzeiliges Zeilenkommentar beginnt mit den Zeichen // und erstreckt sich bis zum Ende der Zeile.

Beispiel: Das Beispiel

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

enthält mehrere einzeilige Kommentare.

Endbeispiel

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
    ;

Kommentare werden nicht geschachtelt. Die Zeichenfolgen /* und */ verfügen innerhalb eines einzeiligen Kommentars über keine besondere Bedeutung, und die Zeichenfolgen // und /* verfügen in einem durch Trennzeichen getrennten Kommentar über keine besondere Bedeutung.

Kommentare werden nicht innerhalb von Zeichen- und Zeichenfolgenliteralen verarbeitet.

Hinweis: Diese Regeln müssen sorgfältig interpretiert werden. Im folgenden Beispiel wird beispielsweise der durch Trennzeichen getrennte Kommentar, der vor dem A Ende zwischen B und .C() Der Grund dafür ist, dass

// B */ C();

ist nicht eigentlich ein einzelner Zeilenkommentar, da // er innerhalb eines durch Trennzeichen getrennten Kommentars keine besondere Bedeutung hat und daher */ in dieser Zeile die übliche besondere Bedeutung hat.

Ebenso beginnt der durch Trennzeichen getrennte Kommentar vor dem Ende vor D E. Der Grund dafür ist, dass es "D */ " sich nicht tatsächlich um ein Zeichenfolgenliteral handelt, da das anfängliche doppelte Anführungszeichen in einem durch Trennzeichen getrennten Kommentar angezeigt wird.

Eine nützliche Folge von /* und */ ohne besondere Bedeutung innerhalb eines einzelnen Zeilenkommentar besteht darin, dass ein Quellcodeblock auskommentiert werden kann, indem // er am Anfang jeder Zeile steht. Im Allgemeinen funktioniert es nicht, vor diesen Zeilen und */ danach zu platzieren/*, da dies nicht ordnungsgemäß getrennte Kommentare im Block kapselt und im Allgemeinen die Struktur solcher getrennten Kommentare vollständig ändern kann.

Codebeispiel:

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

Endnote

Single_Line_Comment s und Delimited_Commentmit bestimmten Formaten können als Dokumentationskommentare verwendet werden, wie in §D beschrieben.

6.3.4 Leerzeichen

Leerzeichen werden als beliebiges Zeichen mit Unicode-Klasse Zs (einschließlich Leerzeichen) sowie dem horizontalen Tabstoppzeichen, dem vertikalen Tabstoppzeichen und dem Formularfeedzeichen definiert.

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

6.4 Token

6.4.1 Allgemein

Es gibt mehrere Arten von Token: Bezeichner, Schlüsselwörter, Literale, Operatoren und Satzzeichen. Leerzeichen und Kommentare sind keine Token, obwohl sie als Trennzeichen für Token fungieren.

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

Hinweis: Dies ist eine ANTLR-Parserregel, sie definiert kein lexikalisches Token, sondern die Sammlung von Tokentypen. Endnote

6.4.2 Unicode-Zeichen escapesequenzen

Eine Unicode-Escapesequenz stellt einen Unicode-Codepunkt dar. Unicode-Escapesequenzen werden in Bezeichnern (§6.4.3), Zeichenliteralen (§6.4.5.5), regulären Zeichenfolgenliteralen (§6.4.5.6) und interpolierten regulären Zeichenfolgenausdrücken (§12.8.3) verarbeitet. Eine Unicode-Escapesequenz wird an keiner anderen Stelle verarbeitet (z. B. um einen Operator, einen Satzzeichen oder ein Schlüsselwort zu bilden).

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
    ;

Eine Unicode-Zeichen escapesequenz stellt den einzelnen Unicode-Codepunkt dar, der durch die hexadezimale Zahl nach den Zeichen "\u" oder "\U" gebildet wird. Da C# eine 16-Bit-Codierung von Unicode-Codepunkten in Zeichen- und Zeichenfolgenwerten verwendet, wird ein Unicode-Codepunkt im Bereich U+10000 U+10FFFF mit zwei Unicode-Ersatzcodeeinheiten dargestellt. Unicode-Codepunkte oben U+FFFF sind in Zeichenliteralen nicht zulässig. Unicode-Codepunkte oben U+10FFFF sind ungültig und werden nicht unterstützt.

Mehrere Übersetzungen werden nicht ausgeführt. Beispielsweise entspricht "\u005C" das Zeichenfolgenliteral "\u005Cu005C" anstelle von "\".

Hinweis: Der Unicode-Wert \u005C ist das Zeichen "\". Endnote

Beispiel: Das Beispiel

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

zeigt mehrere Verwendungen von \u0066, die die Escapesequenz für den Buchstaben "f" ist. Das Programm entspricht dem

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

Endbeispiel

6.4.3 Bezeichner

Die Regeln für Bezeichner, die in diesem Unterclause angegeben werden, entsprechen genau denen, die vom Unicode-Standard-Anhang 15 empfohlen werden, außer dass unterstrich als Anfangszeichen zulässig ist (wie in der C-Programmiersprache üblich), Unicode-Escapesequenzen sind in Bezeichnern zulässig, und das "@"-Zeichen ist als Präfix zulässig, damit Schlüsselwörter als Bezeichner verwendet werden können.

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
    ;

Hinweis:

  • Informationen zu den oben genannten Unicode-Zeichenklassen finden Sie im Unicode-Standard.
  • Das Fragment Available_Identifier erfordert den Ausschluss von Schlüsselwörtern und Kontextstichwörtern. Wenn die Grammatik in dieser Spezifikation mit ANTLR verarbeitet wird, wird dieser Ausschluss automatisch von der Semantik von ANTLR behandelt:
    • Schlüsselwörter und Kontextstichwörter treten in der Grammatik als Literalzeichenfolgen auf.
    • ANTLR erstellt implizite lexikalische Tokenregeln werden aus diesen Literalzeichenfolgen erstellt.
    • ANTLR betrachtet diese impliziten Regeln vor den expliziten lexikalischen Regeln in der Grammatik.
    • Daher stimmen Fragment-Available_Identifier nicht mit Schlüsselwörtern oder Kontextstichwörtern überein, da die lexikalischen Regeln für diejenigen, denen sie vorangehen.
  • Fragment Escaped_Identifier enthält Escapestichwörter und Kontextstichwörter, da sie Teil des längeren Tokens sind, beginnend mit einer @ lexikalischen Verarbeitung immer das längste lexikalische Element (§6.3.1).
  • Wie eine Implementierung die Einschränkungen für die zulässigen Unicode_Escape_Sequence Werte erzwingt, ist ein Implementierungsproblem.

Endnote

Beispiel: Beispiele für gültige Bezeichner sind identifier1, _identifier2und @if. Endbeispiel

Ein Bezeichner in einem konformen Programm muss sich im kanonischen Format befinden, das von Unicode Normalization Form C gemäß Unicode Standard Anhang 15 definiert ist. Das Verhalten beim Auftreten eines Bezeichners, der sich nicht in Normalisierungsform C befindet, ist implementierungsdefiniert; Eine Diagnose ist jedoch nicht erforderlich.

Das Präfix "@" ermöglicht die Verwendung von Schlüsselwörtern als Bezeichner, was bei der Interfacierung mit anderen Programmiersprachen nützlich ist. Das Zeichen @ ist nicht tatsächlich Teil des Bezeichners, sodass der Bezeichner in anderen Sprachen als normaler Bezeichner ohne das Präfix angezeigt wird. Ein Bezeichner mit einem @ Präfix wird als Verbatimbezeichner bezeichnet.

Hinweis: Die Verwendung des @ Präfixes für Bezeichner, die keine Schlüsselwörter sind, ist zulässig, es wird jedoch dringend davon abgeraten, stilmäßig zu arbeiten. Endnote

Beispiel: Das Beispiel:

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

definiert eine Klasse namens "class" mit einer statischen Methode namens "static", die einen Parameter namens "bool" verwendet. Beachten Sie, dass Unicode-Escapes in Schlüsselwörtern nicht zulässig sind, ist das Token "cl\u0061ss" ein Bezeichner und ist derselbe Bezeichner wie "@class".

Endbeispiel

Zwei Bezeichner werden als identisch betrachtet, wenn sie nach der Anwendung der folgenden Transformationen identisch sind, in der Reihenfolge:

  • Das Präfix "@", falls verwendet, wird entfernt.
  • Jede Unicode_Escape_Sequence wird in das entsprechende Unicode-Zeichen umgewandelt.
  • Alle Formatting_Characterwerden entfernt.

Die Semantik eines benannten _ Bezeichners hängt vom Kontext ab, in dem er angezeigt wird:

  • Sie kann ein benanntes Programmelement, z. B. eine Variable, eine Klasse oder eine Methode, oder
  • Sie kann einen Verwerfen (§9.2.9.1) kennzeichnen.

Bezeichner mit zwei aufeinander folgenden Unterstrichzeichen (U+005F) sind für die Verwendung durch die Implementierung reserviert. Es ist jedoch keine Diagnose erforderlich, wenn ein solcher Bezeichner definiert ist.

Hinweis: Eine Implementierung kann z. B. erweiterte Schlüsselwörter bereitstellen, die mit zwei Unterstrichen beginnen. Endnote

6.4.4 Schlüsselwörter

Bei einem Schlüsselwort handelt es sich um eine bezeichnerähnliche Abfolge von Zeichen, die reserviert sind und nicht als Bezeichner verwendet werden können, mit Ausnahme der Vorschrift des @ Zeichens.

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

Ein kontextbezogenes Schlüsselwort ist eine bezeichnerähnliche Abfolge von Zeichen, die in bestimmten Kontexten eine besondere Bedeutung haben, aber nicht reserviert sind, und kann als Bezeichner außerhalb dieser Kontexte sowie beim Vorwort des @ Zeichens verwendet werden.

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

Hinweis: Das Schlüsselwort "rules" und "contextual_keyword" sind Parserregeln, da sie keine neuen Tokentypen einführen. Alle Schlüsselwörter und Kontextstichwörter werden durch implizite lexikalische Regeln definiert, da sie als Literalzeichenfolgen in der Grammatik (§6.2.3) auftreten. Endnote

In den meisten Fällen ist die syntaktische Position kontextbezogener Schlüsselwörter so, dass sie niemals mit der normalen Bezeichnerverwendung verwechselt werden können. In einer Eigenschaftsdeklaration haben die get Bezeichner set beispielsweise eine besondere Bedeutung (§15.7.3). Ein anderer Bezeichner als get oder set niemals an diesen Speicherorten zulässig, sodass diese Verwendung nicht mit einer Verwendung dieser Wörter als Bezeichner in Konflikt steht.

In bestimmten Fällen reicht die Grammatik nicht aus, um die Kontextwortverwendung von Bezeichnern zu unterscheiden. In allen solchen Fällen wird angegeben, wie zwischen den beiden mehrdeutig zu unterscheiden ist. Das Kontextschlüsselwort var in implizit typierten lokalen Variablendeklarationen (§13.6.2) kann z. B. mit einem deklarierten varTyp in Konflikt geraten. In diesem Fall hat der deklarierte Name Vorrang vor der Verwendung des Bezeichners als kontextbezogenes Schlüsselwort.

Ein weiteres Beispiel für eine solche Mehrdeutigkeit ist das kontextbezogene Schlüsselwort await (§12.9.8.1), das nur dann als Schlüsselwort betrachtet wird, wenn eine Methode deklariert ist async, aber an anderer Stelle als Bezeichner verwendet werden kann.

Ebenso wie bei Schlüsselwörtern können Kontextstichwörter als gewöhnliche Bezeichner verwendet werden, indem sie dem @ Zeichen vorangestellt werden.

Hinweis: Bei Verwendung als kontextbezogene Schlüsselwörter können diese Bezeichner nicht Unicode_Escape_Sequences enthalten. Endnote

6.4.5 Literale

6.4.5.1 Allgemein

Ein Literal (§12.8.2) ist eine Quellcodedarstellung eines Werts.

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

Hinweis: Literal ist eine Parserregel, da sie andere Tokentypen gruppiert und keine neue Tokenart einführt. Endnote

6.4.5.2 Boolesche Literale

Es gibt zwei boolesche Literalwerte: true und false.

boolean_literal
    : TRUE
    | FALSE
    ;

Hinweis: boolean_literal ist eine Parserregel, da sie andere Tokentypen gruppiert und keine neue Tokenart einführt. Endnote

Der Typ eines boolean_literal lautet bool.

6.4.5.3 Ganzzahlige Literale

Ganzzahlige Literale werden verwendet, um Werte von Typen int, , uint, longund ulong. Ganzzahlige Literale weisen drei mögliche Formen auf: dezimal, hexadezimal und binär.

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

Der Typ eines ganzzahligen Literals wird wie folgt bestimmt:

  • Wenn das Literal kein Suffix aufweist, weist es die ersten dieser Typen auf, in denen sein Wert dargestellt werden kann: int, , , uintlong. ulong
  • Wenn das Literal durch U oder u, hat es den ersten dieser Typen, in denen sein Wert dargestellt werden kann: uint, ulong.
  • Wenn das Literal durch L oder l, hat es den ersten dieser Typen, in denen sein Wert dargestellt werden kann: long, ulong.
  • Wenn das Literal durch UL, , , uL, ul, , LU, Lu, , lU, oder lu, ist es vom Typ ulongUl.

Wenn der durch eine ganze Zahl dargestellte Wert außerhalb des ulong Typbereichs liegt, tritt ein Kompilierungszeitfehler auf.

Hinweis: Im Stil wird vorgeschlagen, dass "L" anstelle von "l" beim Schreiben von Literalen des Typs longverwendet wird, da es leicht ist, den Buchstaben "l" mit der Ziffer "1" zu verwechseln. Endnote

Damit die kleinsten int und long Werte als ganze Literale geschrieben werden können, sind die folgenden beiden Regeln vorhanden:

  • Wenn ein Integer_Literal , der den Wert 2147483648 darstellt (2¹¹) und kein Integer_Type_Suffix als Token angezeigt wird, das unmittelbar auf ein unäres Minusoperatortoken (§12.9.3) folgt, ist das Ergebnis (von beiden Token) eine Konstante des Typs int mit dem Wert −2147483648 (−2¹¹). In allen anderen Situationen ist eine solche Integer_Literal vom Typ uint.
  • Wenn ein Integer_Literal, der den Wert 9223372036854775808 (2⁶¹) darstellt und kein Integer_Type_Suffix oder das Integer_Type_Suffix L oder l als Token unmittelbar nach einem unären Minusoperatortoken (§12.9.3) angezeigt wird, ist das Ergebnis (von beiden Token) eine Konstante des Typs long mit dem Wert −9223372036854775808 (−2⁶⁶⁶¹). In allen anderen Situationen ist eine solche Integer_Literal vom Typ ulong.

Beispiel:

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

Endbeispiel

6.4.5.4 Reale Literale

Echte Literale werden verwendet, um Werte von Typen floatzu schreiben, doubleund 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'
    ;

Wenn kein Real_Type_Suffix angegeben ist, lautet doubleder Typ des Real_Literal . Andernfalls bestimmt die Real_Type_Suffix den Typ des tatsächlichen Literals wie folgt:

  • Ein reales Literalsuffix, das von F oder f vom Typ floatist.

    Beispiel: Die Literale 1f, 1.5f, , 1e10fund 123.456F sind alle Typ float. Endbeispiel

  • Ein reales Literalsuffix, das von D oder d vom Typ doubleist.

    Beispiel: Die Literale 1d, 1.5d, , 1e10dund 123.456D sind alle Typ double. Endbeispiel

  • Ein reales Literalsuffix, das von M oder m vom Typ decimalist.

    Beispiel: Die Literale 1m, 1.5m, , 1e10mund 123.456M sind alle Typ decimal. Endbeispiel
    Dieses Literal wird in einen decimal Wert konvertiert, indem der genaue Wert verwendet wird und ggf. auf den nächsten darstellbaren Wert gerundet wird, indem die Rundung des Bankers verwendet wird (§8.3.8). Alle im Literal sichtbaren Skalierungen bleiben erhalten, es sei denn, der Wert wird gerundet. Hinweis: Daher wird das Literal 2.900m analysiert, um das decimal mit Vorzeichen 0, Koeffizienten 2900und Skalierung 3zu bilden. Endnote

Wenn die Größe des angegebenen Literals zu groß ist, um im angegebenen Typ dargestellt zu werden, tritt ein Kompilierungszeitfehler auf.

Hinweis: Insbesondere wird ein Real_Literal niemals eine Gleitkomma-Unendlichkeit erzeugen. Eine nicht null Real_Literal kann jedoch auf Null gerundet werden. Endnote

Der Wert eines echten Literals oder float double wird mithilfe des IEC 605559-Modus "runden auf nächste" Modus mit Bindungen, die mit "even" (einem Wert mit der kleinsten signifikanten Bit-Null) und allen Ziffern bestimmt werden, die als signifikant eingestuft wurden.

Hinweis: In einem echten Literal sind nach dem Dezimalkomma immer Dezimalstellen erforderlich. Ist z. B. ein echtes Literal, 1.3F ist aber 1.F nicht. Endnote

Beispiel:

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

Endbeispiel

6.4.5.5 Zeichenliterale

Ein Zeichenliteral stellt ein einzelnes Zeichen dar und besteht aus einem Zeichen in Anführungszeichen, wie 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?
    ;

Hinweis: Ein Zeichen, das einem umgekehrten Schrägstrich (\) in einem Zeichen folgt, muss eines der folgenden Zeichen sein: ', , , ", \, , fbtna0uUr, . . vx Andernfalls tritt ein Kompilierungsfehler auf. Endnote

Hinweis: Die Verwendung der \x Hexadecimal_Escape_Sequence Produktion kann aufgrund der variablen Anzahl von Hexadezimalziffern nach der \xZeichenfolge fehleranfällig und schwer zu lesen sein. Beispiel:

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

Es kann zuerst vorkommen, dass das führende Zeichen in beiden Zeichenfolgen gleich (U+0009ein Tabstoppzeichen) ist. Tatsächlich beginnt die zweite Zeichenfolge mit U+9BAD allen drei Buchstaben im Wort "Schlecht" als gültige Hexadezimalziffern. Im Sinne des Stils empfiehlt es sich, entweder \x bestimmte Escapesequenzen (\t in diesem Beispiel) oder die Escapesequenz mit fester Länge \u zu vermeiden.

Endnote

Eine hexadezimale Escapesequenz stellt eine einzelne Unicode UTF-16-Codeeinheit dar, wobei der Wert durch die hexadezimale Zahl nach "\x" gebildet wird.

Wenn der durch ein Zeichenliteral dargestellte Wert größer als U+FFFFist, tritt ein Kompilierungszeitfehler auf.

Eine Unicode-Escapesequenz (§6.4.2) in einem Zeichenliteral muss sich im Bereich U+0000 befinden.U+FFFF

Eine einfache Escapesequenz stellt ein Unicode-Zeichen dar, wie in der folgenden Tabelle beschrieben.

Escapesequenz Zeichenname Unicode-Codepunkt
\' Einfaches Anführungszeichen U+0027
\" Doppeltes Anführungszeichen U+0022
\\ Backslash U+005C
\0 Null U+0000
\a Warnung U+0007
\b Rückschritt U+0008
\f Seitenvorschub U+000C
\n Zeilenwechsel U+000A
\r Wagenrücklauf U+000D
\t Horizontaler Tabulator U+0009
\v Vertikaler Tabulator U+000B

Der Typ eines Character_Literal lautet char.

6.4.5.6 Zeichenfolgenliterale

C# unterstützt zwei Formen von Zeichenfolgenliteralen: reguläre Zeichenfolgenliterale und verbatim Zeichenfolgenliterale. Ein reguläres Zeichenfolgenliteral besteht aus null oder mehr Zeichen, die in doppelte Anführungszeichen eingeschlossen sind, wie in "hello", und kann sowohl einfache Escapesequenzen (z \t . B. für das Tabstoppzeichen) als auch Hexadezimal- und Unicode-Escapesequenzen enthalten.

Ein wortseitiges Zeichenfolgenliteral besteht aus einem @ Zeichen, gefolgt von einem doppelten Anführungszeichen, null oder mehr Zeichen und einem schließenden doppelten Anführungszeichen.

Beispiel: Ein einfaches Beispiel ist @"hello". Endbeispiel

In einem verbatimischen Zeichenfolgenliteral werden die Zeichen zwischen den Trennzeichen verbatim interpretiert, wobei es sich nur um eine Quote_Escape_Sequence handelt, die ein doppeltes Anführungszeichen darstellt. Insbesondere werden einfache Escapesequenzen, hexadezimale und Unicode-Escapesequenzen nicht in Zeichenfolgenliteralen verarbeitet. Ein Verbatim-Zeichenfolgenliteral kann mehrere Zeilen umfassen.

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

Beispiel: Das Beispiel

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

zeigt eine Vielzahl von Zeichenfolgenliteralen an. Das letzte Zeichenfolgenliteral ist jein verbatimes Zeichenfolgenliteral, das mehrere Zeilen umfasst. Die Zeichen zwischen den Anführungszeichen, einschließlich Leerzeichen wie neuen Zeilenzeichen, bleiben unverändert, und jedes Paar von doppelten Anführungszeichen wird durch ein solches Zeichen ersetzt.

Endbeispiel

Hinweis: Alle Zeilenumbrüche innerhalb von Verbatim-Zeichenfolgenliteralen sind Teil der resultierenden Zeichenfolge. Wenn die genauen Zeichen, die zum Erstellen von Zeilenumbrüchen verwendet werden, semantisch für eine Anwendung relevant sind, ändern alle Tools, die Zeilenumbrüche im Quellcode in verschiedene Formate übersetzen (z. B. zwischen "\n" und "\r\n"), das Anwendungsverhalten. Entwickler sollten in solchen Situationen vorsichtig sein. Endnote

Hinweis: Da eine hexadezimale Escapesequenz eine variable Anzahl von Hexadezimalziffern aufweisen kann, enthält das Zeichenfolgenliteral "\x123" ein einzelnes Zeichen mit hexem Wert 123. Um eine Zeichenfolge zu erstellen, die das Zeichen mit hexem Wert 12 enthält, gefolgt von dem Zeichen 3, könnte man schreiben "\x00123" oder "\x12" + "3" stattdessen. Endnote

Der Typ eines String_Literal lautet string.

Jedes Zeichenfolgenliteral führt nicht unbedingt zu einer neuen Zeichenfolgeninstanz. Wenn zwei oder mehr Zeichenfolgenliterale, die dem Zeichenfolgengleichstellungsoperator (§12.12.8) entsprechen, in derselben Assembly angezeigt werden, verweisen diese Zeichenfolgenliterale auf dieselbe Zeichenfolgeninstanz.

Beispiel: Beispielsweise die ausgabe, die von

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

liegt True daran, dass die beiden Literale auf dieselbe Zeichenfolgeninstanz verweisen.

Endbeispiel

6.4.5.7 Das Nullliteral

null_literal
    : NULL
    ;

Hinweis: null_literal ist eine Parserregel, da keine neue Tokenart eingeführt wird. Endnote

Ein null_literal stellt einen null Wert dar. Er verfügt nicht über einen Typ, kann jedoch über eine Literalkonvertierung (§10.2.7) in einen beliebigen Bezugstyp oder nullwertfähigen Werttyp konvertiert werden.

6.4.6 Operatoren und Interpunktionszeichen

Es gibt verschiedene Arten von Operatoren und Trennzeichen. Operatoren werden in Ausdrücken verwendet, um Vorgänge mit einem oder mehreren Operanden zu beschreiben.

Beispiel: Der Ausdruck a + b verwendet den + Operator, um die beiden Operanden a bund . Endbeispiel

Trennzeichen werden zum Gruppieren und Trennen verwendet.

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

right_shift
    : '>'  '>'
    ;

right_shift_assignment
    : '>' '>='
    ;

Hinweis: right_shift und right_shift_assignment sind Parserregeln, da sie keine neue Tokenart einführen, sondern eine Abfolge von zwei Token darstellen. Die operator_or_punctuator Regel ist nur zu beschreibenden Zwecken vorhanden und wird nicht an anderer Stelle in der Grammatik verwendet. Endnote

right_shift besteht aus den beiden Token > und >. Ebenso besteht right_shift_assignment aus den beiden Token > und >=. Im Gegensatz zu anderen Produktionen in der syntaktischen Grammatik sind keine Zeichen jeglicher Art (nicht einmal Leerzeichen) zwischen den beiden Token in jeder dieser Produktionen zulässig. Diese Produktionen werden speziell behandelt, um die korrekte Handhabung von type_parameter_lists (§15.2.3) zu ermöglichen.

Hinweis: Vor dem Hinzufügen von Generika zu C# >> und >>= waren beide einzelne Token. Die Syntax für Generika verwendet jedoch die < Und-Zeichen > zum Trennen von Typparametern und Typargumenten. Es ist oft wünschenswert, geschachtelte konstruierte Typen zu verwenden, z List<Dictionary<string, int>>. B. . Die Definition der beiden operator_or_punctuator wurde geändert, anstatt den Programmierer zu trennen und durch ein Leerzeichen zu trennen > >. Endnote

6.5 Richtlinien vor der Verarbeitung

6.5.1 Allgemein

Die Vorverarbeitungsdirektiven bieten die Möglichkeit, Abschnitte der Kompilierungseinheiten bedingt zu überspringen, Fehler- und Warnbedingungen zu melden, unterschiedliche Quellcodebereiche zu delineieren und den nullfähigen Kontext festzulegen.

Hinweis: Der Begriff "Vorverarbeitungsdirektiven" wird nur für Konsistenz mit den Programmiersprachen C und C++ verwendet. In C# gibt es keinen separaten Vorverarbeitungsschritt. Vorverarbeitungsdirektiven werden im Rahmen der lexikalischen Analysephase verarbeitet. Endnote

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
    ;

Hinweis:

  • Die Grammatik vor dem Prozessor definiert ein einzelnes lexikalisches Token PP_Directive , das für alle Präverarbeitungsdirektiven verwendet wird. Die Semantik der einzelnen Vorverarbeitungsdirektiven wird in dieser Sprachspezifikation definiert, aber nicht, wie sie implementiert werden.
  • Das PP_Start Fragment darf nur zu Beginn einer Zeile erkannt werden, das getCharPositionInLine() == 0 lexikalische ANTLR-Prädikat oben schlägt eine Möglichkeit vor, in der dies erreicht werden kann und nur informativ ist, eine Implementierung kann eine andere Strategie verwenden.

Endnote

Die folgenden Vorverarbeitungsdirektiven sind verfügbar:

  • #define und #undef, die zum Definieren bzw. Rückgängigmachen von Symbolen für die bedingte Kompilierung verwendet werden (§6.5.4).
  • #if, #elif, #elseund #endif, die verwendet werden, um bedingte Abschnitte des Quellcodes zu überspringen (§6.5.5).
  • #line, die zur Steuerung von Zeilennummern verwendet wird, die für Fehler und Warnungen (§6.5.8) ausgegeben werden.
  • #error, die zum Ausgeben von Fehlern verwendet wird (§6.5.6).
  • #region und #endregion, die verwendet werden, um Abschnitte des Quellcodes explizit zu markieren (§6.5.7).
  • #nullablewird verwendet, um den nullfähigen Kontext (§6.5.9) anzugeben.
  • #pragmawird verwendet, um optionale kontextbezogene Informationen für einen Compiler anzugeben (§6.5.10).

Eine Vorverarbeitungsdirektive belegt immer eine separate Quellcodezeile und beginnt immer mit einem # Zeichen und einem Vorverarbeitungsdirektivenamen. Leerzeichen können vor dem # Zeichen und zwischen dem # Zeichen und dem Direktivennamen auftreten.

Eine Quellzeile, die ein , , , #else#endif#line#if#elifoder #endregioneine Direktive enthält, #nullable kann mit einer einzelnen Zeilenkommentar enden. #undef#define Durch Trennzeichen getrennte Kommentare (die Art von Kommentaren) sind für Quellzeilen, die /* */ Direktiven vor der Verarbeitung enthalten, nicht zulässig.

Präverarbeitungsdirektiven sind nicht Teil der syntaktischen Grammatik von C#. Vorverarbeitungsdirektiven können jedoch verwendet werden, um Sequenzen von Token einzuschließen oder auszuschließen, und dies kann sich auf diese Weise auf die Bedeutung eines C#-Programms auswirken.

Beispiel: Beim Kompilieren des Programms

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

ergibt genau dieselbe Abfolge von Token wie das Programm.

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

Daher sind die beiden Programme lexikalisch recht unterschiedlich, syntaktisch, sie sind identisch.

Endbeispiel

6.5.2 Symbole für die bedingte Kompilierung

Die bedingte Kompilierungsfunktion, die von den #ifDirektiven bereitgestellt wird, #elif#endif #elsewird durch Präverarbeitungsausdrücke (§6.5.3) und bedingte Kompilierungssymbole gesteuert.

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

Beachten Sie , wie eine Implementierung die Einschränkung für die zulässigen Basic_Identifier Werte erzwingt, ist ein Implementierungsproblem. Endnote

Zwei bedingte Kompilierungssymbole werden als identisch betrachtet, wenn sie nach anwendung der folgenden Transformationen identisch sind, in der reihenfolge:

  • Jede Unicode_Escape_Sequence wird in das entsprechende Unicode-Zeichen umgewandelt.
  • Alle Formatting_Characters werden entfernt.

Ein Symbol für die bedingte Kompilierung weist zwei mögliche Zustände auf: definiert oder nicht definiert. Am Anfang der lexikalischen Verarbeitung einer Kompilierungseinheit ist ein Symbol für die bedingte Kompilierung nicht definiert, es sei denn, es wurde explizit durch einen externen Mechanismus definiert (z. B. eine Befehlszeilencompileroption). Wenn eine #define Direktive verarbeitet wird, wird das in dieser Direktive genannte Symbol für die bedingte Kompilierung in dieser Kompilierungseinheit definiert. Das Symbol bleibt so lange definiert, bis eine #undef Direktive für dasselbe Symbol verarbeitet wird oder bis das Ende der Kompilierungseinheit erreicht ist. Dies bedeutet, dass #define und #undef Direktiven in einer Kompilierungseinheit keine Auswirkungen auf andere Kompilierungseinheiten im selben Programm haben.

Wenn in einem Vorverarbeitungsausdruck (§6.5.3) verwiesen wird, weist ein definiertes Symbol für die bedingte Kompilierung den booleschen Wert trueauf, und ein nicht definiertes Symbol für die bedingte Kompilierung weist den booleschen Wert falseauf. Es ist nicht erforderlich, dass bedingte Kompilierungssymbole explizit deklariert werden, bevor sie in Vorverarbeitungsausdrücken referenziert werden. Stattdessen sind nicht deklarierte Symbole einfach nicht definiert und weisen somit den Wert falseauf.

Der Namespace für bedingte Kompilierungssymbole unterscheidet sich und unterscheidet sich von allen anderen benannten Entitäten in einem C#-Programm. Auf bedingte Kompilierungssymbole kann nur in #define direktiven und #undef in Vorverarbeitungsausdrücken verwiesen werden.

6.5.3 Vorverarbeitung von Ausdrücken

Vorverarbeitungsausdrücke können in #if und #elif Direktiven auftreten. Die Operatoren ! (nur logische Negation mit Präfix), ==, !=, &&, und || sind in Vorverarbeitungsausdrücken zulässig, und Klammern können zum Gruppieren verwendet werden.

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

Wenn in einem Vorverarbeitungsausdruck verwiesen wird, weist ein definiertes Symbol für die bedingte Kompilierung den booleschen Wert trueauf, und ein nicht definiertes Symbol für die bedingte Kompilierung weist den booleschen Wert falseauf.

Die Auswertung eines Vorverarbeitungsausdrucks liefert immer einen booleschen Wert. Die Auswertungsregeln für einen Vorverarbeitungsausdruck sind identisch mit denen für einen Konstantenausdruck (§12.23), mit der Ausnahme, dass die einzigen benutzerdefinierten Entitäten, auf die verwiesen werden kann, bedingte Kompilierungssymbole sind.

6.5.4 Definitionsrichtlinien

Die Definitionsdirektiven werden verwendet, um bedingte Kompilierungssymbole zu definieren oder rückgängig zu machen.

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

Die Verarbeitung einer #define Direktive bewirkt, dass das angegebene Symbol für die bedingte Kompilierung definiert wird, beginnend mit der Quellzeile, die der Direktive folgt. Ebenso bewirkt die Verarbeitung einer #undef Direktive, dass das angegebene Symbol für die bedingte Kompilierung nicht definiert wird, beginnend mit der Quellzeile, die der Direktive folgt.

Alle #define Anweisungen #undef in einer Kompilierungseinheit erfolgen vor dem ersten Token (§6.4) in der Kompilierungseinheit. Andernfalls tritt ein Kompilierungsfehler auf. In intuitiven Worten #define , und #undef Direktiven müssen jedem "echten Code" in der Kompilierungseinheit vorangehen.

Beispiel: Das Beispiel:

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

ist gültig, da die #define Direktiven dem ersten Token (dem namespace Schlüsselwort) in der Kompilierungseinheit vorangehen.

Endbeispiel

Beispiel: Das folgende Beispiel führt zu einem Kompilierungszeitfehler, da ein #define realem Code folgt:

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

Endbeispiel

Ein #define Kann ein bereits definiertes Symbol für die bedingte Kompilierung definieren, ohne dass für dieses Symbol einGegriffen werden #undef muss.

Beispiel: Im folgenden Beispiel wird ein Symbol für die bedingte Kompilierung A definiert und anschließend erneut definiert.

#define A
#define A

Für Compiler, die die Definition von Symbolen für die bedingte Kompilierung als Kompilierungsoptionen ermöglichen, besteht eine alternative Möglichkeit für eine solche Neudefinition darin, das Symbol als Compileroption sowie in der Quelle zu definieren.

Endbeispiel

Ein #undef bedingtes Kompilierungssymbol kann "rückgängig machen", das nicht definiert ist.

Beispiel: Im folgenden Beispiel wird ein Symbol für die bedingte Kompilierung A definiert und dann zweimal rückgängig gemacht, obwohl die zweite #undef keine Auswirkung hat, ist es weiterhin gültig.

#define A
#undef A
#undef A

Endbeispiel

6.5.5 Richtlinien für die bedingte Kompilierung

Die Richtlinien für die bedingte Kompilierung werden verwendet, um Teile einer Kompilierungseinheit bedingt einzuschließen oder auszuschließen.

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

Richtlinien zur bedingten Kompilierung müssen in Gruppen geschrieben werden, die aus einer Richtlinie, null #if oder mehr #elif Richtlinien, null oder einer #else Richtlinie und einer #endif Richtlinie bestehen. Zwischen den Direktiven handelt es sich um bedingte Abschnitte des Quellcodes . Jeder Abschnitt wird durch die unmittelbar vorangehende Direktive gesteuert. Ein bedingter Abschnitt kann selbst geschachtelte Richtlinien für die bedingte Kompilierung enthalten, vorausgesetzt, diese Direktiven bilden vollständige Gruppen.

Mindestens einer der enthaltenen bedingten Abschnitte wird für die normale lexikalische Verarbeitung ausgewählt:

  • Die PP_Expression der richtlinien und #elif der #if PP_Expressionwerden in der Reihenfolge ausgewertet, bis eine ergibttrue. Wenn ein Ausdruck zur Folge hat true, wird der bedingte Abschnitt nach der entsprechenden Direktive ausgewählt.
  • Wenn alle PP_Expressionertragen falseund wenn eine #else Direktive vorhanden ist, wird der bedingte Abschnitt nach der #else Direktive ausgewählt.
  • Andernfalls ist kein bedingter Abschnitt ausgewählt.

Der ausgewählte bedingte Abschnitt wird, sofern vorhanden, als normale input_section verarbeitet: Der im Abschnitt enthaltene Quellcode entspricht der lexikalischen Grammatik; Token werden aus dem Quellcode im Abschnitt generiert; und die Vorverarbeitungsdirektiven im Abschnitt haben die vorgeschriebenen Auswirkungen.

Alle verbleibenden bedingten Abschnitte werden übersprungen, und es werden keine Token mit Ausnahme von Direktiven vor der Verarbeitung aus dem Quellcode generiert. Aus diesem Grund können übersprungener Quellcode, mit Ausnahme von Direktiven vor der Verarbeitung, lexikalisch falsch sein. Übersprungene Vorverarbeitungsrichtlinien sind lexikalisch korrekt, werden aber nicht anderweitig verarbeitet. Innerhalb eines bedingten Abschnitts, der alle geschachtelten bedingten Abschnitte (in geschachtelten #if...#endif Konstrukten) übersprungen wird, werden ebenfalls übersprungen.

Hinweis: Die oben genannte Grammatik erfasst nicht die Zuteilung, dass die bedingten Abschnitte zwischen den Vorverarbeitungsdirektiven lexikalisch falsch formatiert sein können. Daher ist die Grammatik nicht ANTLR-ready, da sie nur lexikalisch korrekte Eingaben unterstützt. Endnote

Beispiel: Das folgende Beispiel veranschaulicht, wie bedingte Kompilierungsdirektiven geschachtelt werden können:

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

Mit Ausnahme von Vorverarbeitungsdirektiven unterliegt übersprungener Quellcode nicht der lexikalischen Analyse. Beispielsweise gilt Folgendes trotz des unterminierten Kommentars im #else Abschnitt:

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

Beachten Sie jedoch, dass Vorverarbeitungsdirektiven auch in übersprungenen Abschnitten des Quellcodes lexikalisch korrekt sein müssen.

Vorverarbeitungsdirektiven werden nicht verarbeitet, wenn sie in mehrzeiligen Eingabeelementen angezeigt werden. Beispiel:

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

ergibt die Ausgabe:

hello,
#if Debug
        world
#else
        Nebraska
#endif

In besonderen Fällen kann der Satz von Vorverarbeitungsrichtlinien, die verarbeitet werden, von der Bewertung der pp_expression abhängen. Beispiel:

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

erzeugt immer denselben Tokendatenstrom (class }Q { ), unabhängig davon, ob X definiert ist. Wenn X definiert ist, sind #if die einzigen verarbeiteten Direktiven und #endif, aufgrund der multi-Zeilenkommentar. Wenn X nicht definiert ist, sind drei Direktiven (#if, #else, #endif) Teil des Richtliniensatzes.

Endbeispiel

6.5.6 Diagnosedirektiven

Die Diagnosedirektiven werden verwendet, um explizit Fehler- und Warnmeldungen zu generieren, die auf die gleiche Weise wie andere Kompilierungszeitfehler und -warnungen gemeldet werden.

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

fragment PP_Message
    : PP_Whitespace Input_Character*
    ;

Beispiel: Das Beispiel

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

erzeugt einen Kompilierungsfehler ("Ein Build kann nicht sowohl Debug- als auch Verkaufsvorgang sein"), wenn die Symbole Debug für die bedingte Kompilierung definiert sind und Retail beide definiert sind. Beachten Sie, dass ein PP_Message beliebigen Text enthalten kann; insbesondere muss es keine wohlgeformten Token enthalten, wie durch das einzelne Anführungszeichen im Wort can'tdargestellt.

Endbeispiel

6.5.7 Regionsrichtlinien

Die Regionsdirektiven werden verwendet, um explizite Bereiche des Quellcodes zu kennzeichnen.

fragment PP_Region
    : PP_Start_Region
    | PP_End_Region
    ;

fragment PP_Start_Region
    : 'region' PP_Message?
    ;

fragment PP_End_Region
    : 'endregion' PP_Message?
    ;

An einen Bereich wird keine semantische Bedeutung angefügt; Regionen sind für die Verwendung durch den Programmierer oder durch automatisierte Tools zum Markieren eines Quellcodeabschnitts vorgesehen. Es muss eine #endregion Richtlinie vorhanden sein, die jeder #region Richtlinie entspricht. Die in einer #region Oder #endregion Direktive angegebene Nachricht hat ebenfalls keine semantische Bedeutung, sondern dient lediglich dazu, die Region zu identifizieren. Abgleich #region und #endregion Direktiven können unterschiedliche PP_Messagehaben.

Die lexikalische Verarbeitung einer Region:

#region
...
#endregion

entspricht exakt der lexikalischen Verarbeitung einer bedingte Kompilierungsdirektive des Formulars:

#if true
...
#endif

Hinweis: Dies bedeutet, dass ein Bereich einen oder #ifmehrere /.../#endif- oder in einem bedingten Abschnitt innerhalb eines #if/.../#endifenthalten sein kann. Ein Bereich kann sich jedoch nicht mit einem teil eines #if/.../#endif- oder start& end in verschiedenen bedingten Abschnitten überlappen. Endnote

6.5.8 Leitungsrichtlinien

Zeilendirektiven können verwendet werden, um die Zeilennummern und Kompilierungseinheitsnamen zu ändern, die vom Compiler in der Ausgabe gemeldet werden, z. B. Warnungen und Fehler. Diese Werte werden auch von Caller-Info-Attributen (§22.5.6) verwendet.

Hinweis: Zeilendirektiven werden am häufigsten in Metaprogrammiertools verwendet, die C#-Quellcode aus einer anderen Texteingabe generieren. Endnote

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

Wenn keine #line Direktiven vorhanden sind, meldet der Compiler echte Zeilennummern und Kompilierungseinheitsnamen in der Ausgabe. Bei der Verarbeitung einer #line Direktive, die eine nicht defaultenthaltene PP_Line_Indicator enthält, behandelt der Compiler die Zeile nach der Direktive mit der angegebenen Zeilennummer (und dem Namen der Kompilierungseinheit, sofern angegeben).

Der maximal zulässige Decimal_Digit+ Wert ist durch implementierungsdefiniert.

Durch eine #line default Direktive wird die Wirkung aller vorangehenden #line Direktiven rückgängig. Der Compiler meldet echte Zeileninformationen für nachfolgende Zeilen, genau so, als ob keine #line Direktiven verarbeitet wurden.

Eine #line hidden Direktive hat keine Auswirkungen auf die in Fehlermeldungen gemeldeten Kompilierungseinheit und Zeilennummern oder erzeugt durch Verwendung von CallerLineNumberAttribute (§22.5.6.2). Es soll sich auf Debuggingtools auf Quellebene auswirken, sodass beim Debuggen alle Zeilen zwischen einer #line hidden Direktive und der nachfolgenden #line Direktive (d. h. keine #line hiddenZeilennummerninformationen) enthalten und beim Durchlaufen von Code vollständig übersprungen werden.

Hinweis: Obwohl ein PP_Compilation_Unit_Name Text enthalten kann, der wie eine Escapesequenz aussieht, handelt es sich bei diesem Text nicht um eine Escapesequenz. In diesem Kontext bezeichnet ein "\" Zeichen einfach ein normales umgekehrtes Schrägstrichzeichen. Endnote

6.5.9 Nullfähige Direktive

Die nullable Direktive steuert den nullfähigen Kontext, wie unten beschrieben.

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

Eine nullfähige Direktive legt die verfügbaren Flags für nachfolgende Codezeilen fest, bis eine andere nullfähige Direktive sie überschreibt oder bis zum Ende der Kompilierung _unit erreicht ist. Der nullable Kontext enthält zwei Flags: Anmerkungen und Warnungen. Die Wirkung jeder Form der nullablen Direktive lautet wie folgt:

  • #nullable disable: Deaktiviert sowohl nullable Anmerkungen als auch nullable Warnungskennzeichnungen.
  • #nullable enable: Aktiviert sowohl nullable Anmerkungen als auch nullable Warnungskennzeichnungen.
  • #nullable restore: Stellt sowohl die Anmerkungen als auch Warnungskennzeichnungen in den zustand wieder, der durch den externen Mechanismus angegeben wird, sofern vorhanden.
  • #nullable disable annotations: Deaktiviert das Flag "Nullable Annotations". Das Flag "Nullwerte"-Warnungen ist nicht betroffen.
  • #nullable enable annotations: Aktiviert das Flag "Nullable Annotations". Das Flag "Nullwerte"-Warnungen ist nicht betroffen.
  • #nullable restore annotations: Stellt die Kennzeichnung mit nullablen Anmerkungen auf den zustand wieder her, der durch den externen Mechanismus angegeben wird, sofern vorhanden. Das Flag "Nullwerte"-Warnungen ist nicht betroffen.
  • #nullable disable warnings: Deaktiviert das Flag "Nullwerte"-Warnungen. Das Flag für nullwerte Anmerkungen ist nicht betroffen.
  • #nullable enable warnings: Aktiviert das Flag "Nullable Warnings". Das Flag für nullwerte Anmerkungen ist nicht betroffen.
  • #nullable restore warnings: Stellt ggf. das Flag für nullwerte Warnungen auf den durch den externen Mechanismus angegebenen Zustand wieder her. Das Flag für nullwerte Anmerkungen ist nicht betroffen.

Der nullwerte Status von Ausdrücken wird jederzeit nachverfolgt. Der Status der Anmerkungskennzeichnung und das Vorhandensein oder Fehlen einer nullablen Anmerkung, ?bestimmt den anfänglichen NULL-Zustand einer Variablendeklaration. Warnungen werden nur ausgegeben, wenn die Warnungskennzeichnung aktiviert ist.

Beispiel: Das Beispiel

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

erzeugt eine Kompilierungszeitwarnung ("wie x ist null"). Der Nullable-Zustand wird x überall nachverfolgt. Eine Warnung wird ausgegeben, wenn die Warnungskennzeichnung aktiviert ist.

Endbeispiel

6.5.10 Pragma-Richtlinien

Die #pragma Präverarbeitungsdirektive wird verwendet, um Kontextinformationen für einen Compiler anzugeben.

Hinweis: Beispielsweise kann ein Compiler Direktiven bereitstellen #pragma , die

  • Aktivieren oder Deaktivieren bestimmter Warnmeldungen beim Kompilieren von nachfolgendem Code.
  • Geben Sie an, welche Optimierungen auf nachfolgenden Code angewendet werden sollen.
  • Geben Sie Informationen an, die von einem Debugger verwendet werden sollen.

Endnote

fragment PP_Pragma
    : 'pragma' PP_Pragma_Text?
    ;

fragment PP_Pragma_Text
    : PP_Whitespace Input_Character*
    ;

Die Input_Characters im PP_Pragma_Text werden vom Compiler auf implementierungsdefinierte Weise interpretiert. Die in einer #pragma Richtlinie angegebenen Informationen ändern die Programmsemantik nicht. Eine #pragma Direktive ändert nur das Compilerverhalten, das sich außerhalb des Gültigkeitsbereichs dieser Sprachspezifikation befindet. Wenn der Compiler die Input_Characters nicht interpretieren kann, kann der Compiler eine Warnung erzeugen. Er erzeugt jedoch keinen Kompilierungszeitfehler.

Hinweis: PP_Pragma_Text kann beliebigen Text enthalten; insbesondere müssen keine wohlgeformten Token enthalten. Endnote