次の方法で共有


6 字句構造

6.1 プログラム

C# プログラム は、1 つ以上のソース ファイル (正式には compilation units (§14.2) で構成されます。 コンパイル単位には、ファイル システム内のファイルとの一対一の対応がある場合がありますが、このような対応は必要ありません。

概念的には、プログラムは次の 3 つの手順を使用してコンパイルされます。

  1. 変換。ファイルを特定の文字のレパトアとエンコード スキームから Unicode 文字のシーケンスに変換します。
  2. Unicode 入力文字のストリームをトークンのストリームに変換する字句解析。
  3. 構文分析。トークンのストリームを実行可能コードに変換します。

準拠する実装では、(Unicode 標準で定義されている) UTF-8 エンコード形式でエンコードされた Unicode コンパイル単位を受け入れ、Unicode 文字のシーケンスに変換する必要があります。 実装では、追加の文字エンコード スキーム (UTF-16、UTF-32、Unicode 以外の文字マッピングなど) を受け入れて変換することを選択できます。

: Unicode NULL 文字 (U+0000) の処理は実装で定義されています。 移植性と読みやすさの両方のために、開発者はソース コードでこの文字を使用しないことを強くお勧めします。 文字または文字列リテラル内で文字が必要な場合は、代わりにエスケープ シーケンス \0 または \u0000 を使用できます。 end note

: Unicode 以外の文字表現を使用するファイルを Unicode 文字のシーケンスに変換する方法を定義することは、この仕様の範囲外です。 ただし、このような変換では、他の文字セットの通常の行区切り文字 (またはシーケンス) を、Unicode 復帰文字 (U+000D) とそれに続く Unicode 改行文字 (U+000A) で構成される 2 文字シーケンスに変換することをお勧めします。 ほとんどの場合、この変換には目に見える効果はありません。ただし、逐語的文字列リテラル トークン (§6.4.5.6) の解釈に影響します。 この推奨事項の目的は、Unicode 以外の異なる文字セットをサポートするシステム間でコンパイル単位を移動する場合、特に行区切りに異なる文字シーケンスを使用するシステム間で、逐語的な文字列リテラルが同じ文字シーケンスを生成できるようにすることです。 end note

6.2 文法

6.2.1 全般

この仕様では、2 つの文法を使用した C# プログラミング言語の構文を示します。 非現実的な文法 (§6.2.3) は、Unicode 文字を組み合わせて行終端記号、空白、コメント、トークン、および前処理ディレクティブを形成する方法を定義します。 構文文法 (§6.2.4) は、字句文法から得られるトークンを組み合わせて C# プログラムを形成する方法を定義します。

すべてのターミナル文字は、他の Unicode 文字範囲の類似した文字とは対照的に、U+0020 から U+007F の範囲の適切な Unicode 文字として認識されます。

6.2.2 文法表記

構文文法と構文文法は、ANTLR 文法ツールの Extended Backus-Naur 形式で表示されます。

ANTLR 表記が使用されていますが、この仕様では、C# の完全な ANTLR 対応の "参照文法" は示されていません。手動または ANTLR などのツールを使用して、lexer とパーサーを記述することは、言語仕様の範囲外です。 この修飾により、この仕様は、指定された文法と、ANTLR でレクサーとパーサーを構築するために必要なギャップを最小限に抑えようとします。

ANTLR では、構文と構文の区別、ANTLR による構文パーサー、構文規則の先頭に大文字の構文規則、小文字のパーサー 規則を使用して表記法の文法を区別します。

: C# 字句文法 (§6.2.3) と構文文法 (§6.2.4) は、構文およびパーサー グラマーへの ANTLR 除算と正確に対応していません。 この小さな不一致は、C# 字句文法を指定するときに一部の ANTLR パーサー 規則が使用されることを意味します。 end note

6.2.3 字句文法

C# の字句文法は、 §6.3§6.4、および §6.5 で示されています。 字句文法の終端記号は、Unicode 文字セットの文字です。 構文文法では、文字を結合してトークン (§6.4)、空白 (§6.3.4)、コメント (§6.3.3)、前処理ディレクティブ (§6.5) を形成する方法を指定します。

構文文法のターミナル シンボルの多くは、構文文法のトークンとして明示的に定義されていません。 むしろ、文法内のリテラル文字列が暗黙的な字句トークンとして抽出されるという ANTLR 動作の利点があります。これにより、キーワードや演算子などを、トークン名ではなくリテラル表現で文法で表すことができます。

C# プログラムのすべてのコンパイル単位は、字句文法の input 生成に準拠する必要があります (§6.3.1)。

6.2.4 構文文法

C# の構文文法は、このサブクラウスに従う句、サブクラウス、および付属書に示されています。 構文文法の終端記号は、構文文法によって明示的に定義され、文法自体のリテラル文字列によって暗黙的に定義されるトークンです (§6.2.3)。 構文文法では、トークンを組み合わせて C# プログラムを形成する方法を指定します。

C# プログラムのすべてのコンパイル 単位は、構文文法の compilation_unit 運用 (§14.2) に準拠する必要があります。

6.2.5 文法のあいまいさ

simple_name (§12.8.4) と member_access (§12.8.7) の運用環境では、式の文法があいまいになります。

: ステートメント:

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

は、FG < Aの 2 つの引数を持つB > (7)の呼び出しとして解釈できます。 または、1 つの引数を持つ F の呼び出しとして解釈することもできます。これは、2 つの型引数と 1 つの標準引数を持つジェネリック メソッド G の呼び出しです。

end の例

トークンのシーケンスを simple_name (§12.8.4) として解析できる場合、 member_access (§12.8.7)、または pointer_member_access (§23.6.3) が type_argument_list で終わる (§8.4.2)、終了 > トークンの直後のトークンが調べ、 が表示されるかどうかを確認する

  • ( ) ] } : ; , . ? == != | ^ && || & [のいずれか。
  • 関係演算子の 1 つ < <= >= is as; または
  • クエリ式内に表示されるコンテキスト クエリ キーワード。又は
  • 特定のコンテキストでは、 identifier はあいまいさを解消するトークンとして扱われます。 これらのコンテキストでは、あいまいさが解消されるトークンのシーケンスの直前にキーワード iscase 、または outのいずれかが含まれる場合、またはタプル リテラルの最初の要素 (トークンの前に ( または : 、識別子の後に ,が続く) またはタプル リテラルの後続の要素の解析中に発生します。

次のトークンがこのリスト内にある場合、またはそのようなコンテキスト内の識別子である場合、 type_argument_listsimple_namemember_access 、または pointer_memberアクセスの一部として保持され トークンシーケンスのその他の解析が破棄されます。 それ以外の場合、トークンシーケンスの他の解析が存在しない場合でも、 type_argument_listsimple_namemember_access 、または pointer_member_accessの一部とは見なされません。

: これらの規則は、namespace_or_type_name (§7.8) でtype_argument_listを解析する場合には適用されません。 end note

: ステートメント:

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

は、この規則に従って、1 つの引数を持つ F の呼び出しとして解釈されます。これは、2 つの型引数と 1 つの通常の引数を持つジェネリック メソッド G の呼び出しです。 ステートメント

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

は、それぞれ 2 つの引数を持つ F の呼び出しとして解釈されます。 ステートメント

x = F<A> + y;

は、ステートメントがx = (F < A) > (+y)記述されたかのように、2 項プラス演算子が続くtype_argument_listを持つsimple_nameとしてではなく、より小さい演算子、より大きい演算子、単項プラス演算子として解釈されます。 ステートメント内

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

C<T>トークンは、type_argument_listの後にされた明確でないトークンが存在するため、&&を持つnamespace_or_type_nameとして解釈されます。

(A < B, C > D)式は、それぞれ比較される 2 つの要素を持つタプルです。

(A<B,C> D, E)式は 2 つの要素を持つタプルであり、最初の要素は宣言式です。

呼び出し M(A < B, C > D, E) には 3 つの引数があります。

呼び出し M(out A<B,C> D, E) には 2 つの引数があり、1 つ目は out 宣言です。

e is A<B> C は宣言パターンを使用します。

ケース ラベル case A<B> C: は宣言パターンを使用します。

end の例

relational_expression (§12.12.1) を認識する場合、"relational_expressionistype" と "relational_expressionisconstant_pattern" の両方の代替候補が適用され、typeがアクセス可能な型に解決された場合は、"relational_expressionistype" の代替候補が選択されます。

字句解析

6.3.1 全般

便宜上、字句文法では、次の名前付き lexer トークンが定義され、参照されます。

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

これらは lexer ルールですが、これらの名前は通常の lexer ルール名と区別するために、すべて大文字でスペルされます。

: これらの便利な規則は、リテラル文字列で定義されたトークンに明示的なトークン名を指定しないという通常の方法の例外です。 end note

input production は、C# コンパイル ユニットの字句構造を定義します。

input
    : input_section?
    ;

input_section
    : input_section_part+
    ;

input_section_part
    : input_element* New_Line
    | PP_Directive
    ;

input_element
    : Whitespace
    | Comment
    | token
    ;

: 上記の文法は ANTLR 解析規則によって記述され、構文トークンではなく C# コンパイル ユニットの字句構造を定義します。 end note

C# コンパイルユニットの字句構造を構成する 5 つの基本要素: 行ターミネータ (§6.3.2)、空白 (§6.3.4)、コメント (> §6.3.3)、トークン (§6.4)、および前処理ディレクティブ (§6.5)。 これらの基本的な要素のうち、C# プログラムの構文文法ではトークンのみが重要です (§6.2.4)。

C# コンパイル ユニットの字句処理は、構文分析への入力となる一連のトークンにファイルを減らすことで構成されます。 行ターミネータ、空白文字、コメントは個別のトークンに使用できます。また、プリプロセス ディレクティブによってコンパイル ユニットのセクションがスキップされる可能性がありますが、それ以外の場合、これらの字句要素は C# プログラムの構文構造に影響しません。

複数の字句文法の実稼働がコンパイル単位内の文字のシーケンスと一致する場合、字句処理は常に可能な限り長い字句要素を形成します。

: 文字シーケンス //は、単一の行コメントの先頭として処理されます。これは、構文要素が 1 つの/ トークンよりも長いためです。 end の例

一部のトークンは、一連の字句規則によって定義されます。1 つのメイン ルールと 1 つ以上のサブルール。 後者は、ルールが別のトークンの一部を定義することを示すために、 fragment によって文法でマークされます。 フラグメント ルールは、字句規則の上から下の順序では考慮されません。

: ANTLR では、 fragment はここで定義したのと同じ動作を生成するキーワードです。 end note

6.3.2 ラインターミネータ

行終端記号は、C# コンパイル単位の文字を行に分割します。

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

ファイルの終わりマーカーを追加するソース コード編集ツールとの互換性を保ち、コンパイル 単位を適切に終了した行のシーケンスとして表示できるようにするために、C# プログラムのすべてのコンパイル 単位に次の変換が適用されます。

  • コンパイル単位の最後の文字が Control-Z 文字 (U+001A) の場合、この文字は削除されます。
  • そのコンパイル単位が空でない場合、およびコンパイル単位の最後の文字が復帰 (U+000D)、改行 (U+000A)、次の行文字 (U+0085)、行区切り記号 (U+2028)、または段落区切り記号 (U+2029) でない場合は、復帰文字 (U+000D) がコンパイルユニットの末尾に追加されます。

: 復帰を追加すると、終了New_Lineを持たないPP_Directive (§6.5) でプログラムを終了できます。 end note

6.3.3 コメント

区切りコメントと単一行コメントの 2 つの形式のコメントがサポートされています。

削除されたコメント/*文字で始まり、*/文字で終わります。 区切りコメントは、1 行、1 行、または複数行の一部を占めることができます。

: 例

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

これには区切られたコメントが含まれています。

end の例

single-行コメント//文字で始まり、行の末尾まで拡張されます。

: 例

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

複数の単一行コメントが示されています。

end の例

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
    ;

コメントは入れ子になりません。 文字シーケンス /**/ は単一行コメント内で特別な意味を持ちません。また、///* の文字シーケンスは、区切られたコメント内で特別な意味を持ちません。

コメントは、文字リテラルおよび文字列リテラル内では処理されません。

: これらのルールは慎重に解釈する必要があります。 たとえば、次の例では、 ABC()の間で終了する前に始まる区切りコメントです。 その理由は、

// B */ C();

は実際には単一行コメントではありません。//は区切られたコメント内で特別な意味を持たないため、*/はその行で通常の特別な意味を持ちます。

同様に、区切り記号付きコメントは、Dの前E終了します。 最初の二重引用符文字は区切りコメント内に表示されるため、 "D */ " は実際には文字列リテラルではないためです。

/**/が単一の行コメント内で特別な意味を持たないという便利な結果は、各行の先頭に//を配置することで、ソース コード行のブロックをコメントアウトできることです。 一般に、これらの行の前に /* を配置したり、その後に */ したりすることはできません。これは、区切り記号付きコメントをブロックに適切にカプセル化せず、一般に、このような区切りコメントの構造を完全に変更する可能性があるためです。

コードの例:

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

end note

特定の形式を持つSingle_Line_CommentDelimited_Commentは、§Dで説明されているように文書コメントとして使用できます。

6.3.4 空白

空白は、Unicode クラス Zs (スペース文字を含む) と、水平タブ文字、垂直タブ文字、およびフォーム フィード文字を持つ任意の文字として定義されます。

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

6.4 トークン

6.4.1 全般

トークンには識別子、キーワード、リテラル、演算子、区切り記号など、いくつかの種類があります。 空白とコメントはトークンではありませんが、トークンの区切り記号として機能します。

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

: これは ANTLR パーサー 規則であり、字句トークンではなく、トークンの種類のコレクションを定義します。 end note

6.4.2 Unicode 文字エスケープ シーケンス

Unicode エスケープ シーケンスは、Unicode コード ポイントを表します。 Unicode エスケープ シーケンスは、識別子 (§6.4.3)、文字リテラル (§6.4.5.5)、標準文字列リテラル (§6.4.5.6)、補間された正規表現 (§12.8.3) で処理されます。 Unicode エスケープ シーケンスは、他の場所では処理されません (たとえば、演算子、区切り記号、キーワードを形成するため)。

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
    ;

Unicode 文字エスケープ シーケンスは、"\u" または "\U" 文字の後の 16 進数で形成された 1 つの Unicode コード ポイントを表します。 C# では文字と文字列値に Unicode コード ポイントの 16 ビット エンコードが使用されるため、U+10000するU+10FFFF範囲内の Unicode コード ポイントは、2 つの Unicode サロゲート コード単位を使用して表されます。 U+FFFFの上の Unicode コード ポイントは、文字リテラルでは使用できません。 U+10FFFFの上の Unicode コード ポイントは無効であり、サポートされていません。

複数の翻訳は実行されません。 たとえば、文字列リテラルの"\u005Cu005C"は、"\u005C"ではなく"\"に相当します。

: \u005C Unicode 値は文字 "\" です。 end note

: 例

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

は、文字 "\u0066" のエスケープ シーケンスであるfのいくつかの用途を示しています。 プログラムは次のコードに相当します。

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

end の例

6.4.3 識別子

このサブ文書で指定された識別子の規則は、Unicode 標準付属書 15 で推奨されているものに正確に対応します。ただし、アンダースコアは (C プログラミング言語では従来のように) 最初の文字として許可され、Unicode エスケープ シーケンスは識別子で許可され、キーワードを識別子として使用できるようにするためのプレフィックスとして "@" 文字を使用できます。

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
    ;

:

  • 上記の Unicode 文字クラスについては、「 Unicode 標準」を参照してください。
  • フラグメント Available_Identifier には、キーワードとコンテキスト キーワードの除外が必要です。 この仕様の文法が ANTLR で処理される場合、この除外は ANTLR のセマンティクスによって自動的に処理されます。
    • キーワードとコンテキスト キーワードは、リテラル文字列として文法で使用されます。
    • ANTLR は、これらのリテラル文字列から暗黙的な字句トークン ルールを作成します。
    • ANTLR では、文法の明示的な字句規則の前に、これらの暗黙的な規則が考慮されます。
    • したがって、フラグメント Available_Identifier は、その前にある構文規則としてキーワードやコンテキスト キーワードと一致しません。
  • Fragment Escaped_Identifier には、エスケープされたキーワードとコンテキスト キーワードが含まれます。これは、 @ から始まる長いトークンの一部であり、字句処理は常に可能な限り長い字句要素を形成します (§6.3.1)。
  • 実装で許容される Unicode_Escape_Sequence 値に制限を適用する方法は、実装の問題です。

end note

: 有効な識別子の例として、 identifier1_identifier2、および @ifがあります。 end の例

準拠するプログラムの識別子は、Unicode 標準付属書 15 で定義されている Unicode 正規化形式 C で定義された正規形式である必要があります。 正規化フォーム C にない識別子が検出されたときの動作は、実装で定義されています。ただし、診断は必要ありません。

プレフィックス "@" を使用すると、キーワードを識別子として使用できます。これは、他のプログラミング言語とやり取りするときに便利です。 文字 @ は実際には識別子の一部ではないため、識別子は通常の識別子として、プレフィックスなしで他の言語で見られる場合があります。 @ プレフィックスを持つ識別子は、verbatim 識別子と呼ばれます。

: キーワードではない識別子には @ プレフィックスを使用できますが、スタイルの問題として強くお勧めします。 end note

: 例:

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

は、"class" という名前のクラスを、"static" という名前の静的メソッドで定義し、"bool" という名前のパラメーターを受け取ります。 キーワードでは Unicode エスケープが許可されないため、トークン "cl\u0061ss" は識別子であり、"@class" と同じ識別子であることに注意してください。

end の例

2 つの識別子は、次の変換が適用された後で同一である場合は同じと見なされます。

  • プレフィックス "@" が使用されている場合は削除されます。
  • Unicode_Escape_Sequence は、対応する Unicode 文字に変換されます。
  • すべての Formatting_Characterが削除されます。

_という名前の識別子のセマンティクスは、表示されるコンテキストによって異なります。

  • 変数、クラス、メソッドなどの名前付きプログラム要素を表すことができます。
  • 破棄 (§9.2.9.2) を示す可能性があります。

2 つの連続するアンダースコア文字 (U+005F) を含む識別子は、実装で使用するために予約されています。ただし、このような識別子が定義されている場合は、診断は必要ありません。

: たとえば、実装では、2 つのアンダースコアで始まる拡張キーワードが提供される場合があります。 end note

6.4.4 キーワード

keywordは予約されている識別子に似た文字のシーケンスであり、@文字の前にある場合を除き、識別子として使用することはできません。

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

contextual キーワードは、特定のコンテキストでは特別な意味を持つ文字の識別子に似たシーケンスですが、予約されておらず、それらのコンテキストの外部の識別子として、および@文字の前に付いたときに使用できます。

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

: 規則 keyword および contextual_keyword は、新しいトークンの種類を導入しないため、パーサー ルールです。 すべてのキーワードとコンテキスト キーワードは、文法 (§6.2.3) でリテラル文字列として発生するため、暗黙的な字句規則によって定義されます。 end note

ほとんどの場合、コンテキスト キーワードの構文上の場所は、通常の識別子の使用と混同されないようにするためです。 たとえば、プロパティ宣言内では、 get 識別子と set 識別子には特別な意味があります (§15.7.3)。 これらの場所では、 get または set 以外の識別子は許可されないため、この使用は識別子としてのこれらの単語の使用と競合しません。

場合によっては、文法がコンテキストキーワードの使用法と識別子を区別するのに十分ではありません。 このような場合はすべて、2 つの間のあいまいさを解消する方法を指定します。 たとえば、暗黙的に型指定されたローカル変数宣言 (var) でコンテキスト キーワードが、varと呼ばれる宣言された型と競合する可能性があります。この場合、宣言された名前はコンテキスト キーワードとしての識別子の使用よりも優先されます。

このようなあいまいさのもう 1 つの例は、コンテキスト キーワード await (§12.9.8.1) です。これは、 async宣言されたメソッド内でのみキーワードと見なされますが、他の場所で識別子として使用できます。

キーワードと同様に、コンテキスト キーワードは、 @ 文字の前に付けることで、通常の識別子として使用できます。

: コンテキスト キーワードとして使用する場合、これらの識別子には Unicode_Escape_Sequenceを含めることはできません。 end note

6.4.5 リテラル

6.4.5.1 全般

literal (§12.8.2) は、値のソース コード表現です。

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

: literal は、他のトークンの種類をグループ化し、新しいトークンの種類を導入しないため、パーサールールです。 end note

6.4.5.2 ブールリテラル

ブール型リテラル値には、 truefalseの 2 つがあります。

boolean_literal
    : TRUE
    | FALSE
    ;

: boolean_literal は、他のトークンの種類をグループにし、新しいトークンの種類を導入しないため、パーサー規則です。 end note

boolean_literalの型はbool

6.4.5.3 整数リテラル

整数リテラルは、 intuintlong、および ulongの型の値を書き込むのに使用されます。 整数リテラルには、10 進数、16 進数、バイナリの 3 つの形式があります。

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

整数リテラルの型は、次のように決定されます。

  • リテラルにサフィックスがない場合、その値を表すことができる最初の型 ( intuintlongulong) があります。
  • リテラルのサフィックスが U または u の場合、その値を表すことができる最初の型 ( uintulong) があります。
  • リテラルのサフィックスが L または l の場合、その値を表すことができる最初の型 ( longulong) があります。
  • リテラルのサフィックスが ULUluLulLULulU、または luの場合は、 ulong型です。

整数リテラルで表される値が ulong 型の範囲外の場合は、コンパイル時エラーが発生します。

: スタイルの問題として、L型のリテラルを書き込むときに、"l" ではなく "long" を使用することをお勧めします。これは、文字 "l" を数字 "1" と混同しやすいためです。 end note

可能な限り小さい int 値と long 値を整数リテラルとして書き込むことができるようにするには、次の 2 つの規則が存在します。

  • 値 (2 ²¹) を表す2147483648が、単項マイナス演算子トークン (§12.9.3 の直後にトークンとしてInteger_Type_Suffixが表示されない場合、結果 (両方のトークン) は値が −2147483648 (−2 ¹) の int 型の定数になります。 他のすべての状況では、このような Integer_Literaluint型です。
  • (2⁶ ²) を表す9223372036854775808が、単項マイナス演算子トークン (§12.9.3) の直後にトークンとしてInteger_Type_SuffixまたはLlまたはが表示されない場合、結果 (両方のトークン) は、値が long (−2⁶²) の−9223372036854775808型の定数になります。 他のすべての状況では、このような Integer_Literalulong型です。

例:

123                  // decimal, int
10_543_765Lu         // decimal, ulong
1_2__3___4____5      // decimal, int
_123                 // not a numeric literal; identifier due to leading _
123_                 // invalid; no trailing _allowed

0xFf                 // hex, int
0X1b_a0_44_fEL       // hex, long
0x1ade_3FE1_29AaUL   // hex, ulong
0x_abc               // hex, int
_0x123               // not a numeric literal; identifier due to leading _
0xabc_               // invalid; no trailing _ allowed

0b101                // binary, int
0B1001_1010u         // binary, uint
0b1111_1111_0000UL   // binary, ulong
0B__111              // binary, int
__0B111              // not a numeric literal; identifier due to leading _
0B111__              // invalid; no trailing _ allowed

end の例

6.4.5.4 実際のリテラル

実際のリテラルは、 floatdouble、および 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'
    ;

Real_Type_Suffixが指定されていない場合、Real_Literalの型はdouble。 それ以外の場合、 Real_Type_Suffix は次のように実際のリテラルの型を決定します。

  • Fまたはfによってサフィックスが付いた実際のリテラルは、float型です。

    : リテラル 1f1.5f1e10f123.456F はすべて float型です。 end の例

  • Dまたはdによってサフィックスが付いた実際のリテラルは、double型です。

    : リテラル 1d1.5d1e10d123.456D はすべて double型です。 end の例

  • Mまたはmによってサフィックスが付いた実際のリテラルは、decimal型です。

    : リテラル 1m1.5m1e10m123.456M はすべて decimal型です。 end の例
    このリテラルは、正確な値を取得して decimal 値に変換され、必要に応じて、銀行家の丸め (§8.3.8) を使用して最も近い表現可能な値に丸めます。 リテラルで明らかなスケールは、値が丸められた場合を除き、保持されます。 : そのため、リテラル 2.900mは解析され、符号decimal、係数0、スケール 2900を含む3が形成されます。 end note

指定したリテラルの大きさが大きすぎて、指定された型で表されていない場合は、コンパイル時エラーが発生します。

: 特に、 Real_Literal では浮動小数点の無限大は生成されません。 ただし、0 以外の Real_Literal は 0 に丸められる場合があります。 end note

floatまたはdouble型の実際のリテラルの値は、IEC 60559 の "最も近い値に丸める" モードを使用して決定されます。このモードでは、タイが "偶数" (下位ビット 0 の値) に分割され、すべての数字が有効と見なされます。

: 実際のリテラルでは、小数点の後には常に 10 進数が必要です。 たとえば、 1.3F は実際のリテラルですが、 1.F は実際のリテラルではありません。 end note

例:

1.234_567      // double
.3e5f          // float
2_345E-2_0     // double
15D            // double
19.73M         // decimal
1.F            // parsed as a member access of F due to non-digit after .
1_.2F          // invalid; no trailing _ allowed in integer part
1._234         // parsed as a member access of _234 due to non-digit after .
1.234_         // invalid; no trailing _ allowed in fraction
.3e_5F         // invalid; no leading _ allowed in exponent
.3e5_F         // invalid; no trailing _ allowed in exponent

end の例

6.4.5.5 文字リテラル

文字リテラルは 1 つの文字を表し、 '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?
    ;

: Character で円記号 (\) の後に続く文字は'"\0abfnrtuUxvのいずれかの文字である必要があります。 それ以外の場合は、コンパイル時エラーが発生します。 end note

: \xHexadecimal_Escape_Sequence の実稼働の使用は、 \xに続く 16 進数の可変数のため、エラーが発生しやすく、読みにくい場合があります。 たとえば、コードでは次のようになります。

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

先頭の文字が両方の文字列で同じ (U+0009、タブ文字) であることが最初に表示されることがあります。 実際、2 番目の文字列は U+9BAD で始まります。"Bad" という単語の 3 文字はすべて有効な 16 進数です。 スタイルの問題として、特定のエスケープ シーケンス (この例では\x) または固定長\tエスケープ シーケンスのいずれかを優先して、\uを回避することをお勧めします。

end note

16 進数のエスケープ シーケンスは、単一の Unicode UTF-16 コード単位を表し、値は "\x" の後の 16 進数で形成されます。

文字リテラルで表される値が U+FFFFより大きい場合は、コンパイル時エラーが発生します。

文字リテラル内の Unicode エスケープ シーケンス (§6.4.2) は、U+0000するU+FFFFの範囲内である必要があります。

単純なエスケープ シーケンスは、次の表に示すように Unicode 文字を表します。

エスケープ シーケンス 文字名 Unicode コード ポイント
\' 単一引用符 U+0027
\" 二重引用符 U+0022
\\ 円記号 U+005C
\0 Null U+0000
\a アラート U+0007
\b バックスペース U+0008
\f フォーム フィード U+000C
\n 改行 U+000A
\r キャリッジ リターン U+000D
\t 水平タブ U+0009
\v 垂直タブ U+000B

Character_Literalの型がchar

6.4.5.6 文字列リテラル

C# では、2 つの形式の文字列リテラルがサポートされています。 規則的な文字列リテラルverbatim 文字列リテラル。 通常の文字列リテラルは、 "hello"のように 0 個以上の文字で構成され、単純なエスケープ シーケンス (タブ文字の \t など) と 16 進エスケープ シーケンスと Unicode エスケープ シーケンスの両方を含めることができます。

逐語的な文字列リテラルは、 @ 文字の後に二重引用符文字、0 個以上の文字、および終わりの二重引用符文字で構成されます。

: 簡単な例は @"hello"です。 end の例

逐語的な文字列リテラルでは、区切り記号間の文字は逐語的に解釈され、唯一の例外は 1 つの二重引用符文字を表す Quote_Escape_Sequenceです。 特に、単純なエスケープ シーケンス、および 16 進数および Unicode エスケープ シーケンスは、逐語的な文字列リテラルでは処理されません。 逐語的な文字列リテラルは、複数行にまたがる場合があります。

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

: 例

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

には、さまざまな文字列リテラルが表示されます。 最後の文字列リテラル ( j) は、複数行にまたがる逐語的な文字列リテラルです。 改行文字などの空白を含む引用符の間の文字は逐語的に保持され、二重引用符文字の各ペアはそのような文字に置き換えられます。

end の例

: 逐語的文字列リテラル内のすべての改行は、結果の文字列の一部です。 改行の形式に使用される正確な文字がアプリケーションに意味的に関連している場合、ソース コードの改行を異なる形式 ("\n" と "\r\n" の間など) に変換するツールは、アプリケーションの動作を変更します。 このような状況では、開発者は注意する必要があります。 end note

: 16 進エスケープ・シーケンスには可変数の 16 進数字を含めることができるため、ストリング・リテラル "\x123" には、16 進値 123を持つ 1 文字が含まれます。 16 進値を持つ文字 12 続けて文字 3を含む文字列を作成するには、代わりに "\x00123" または "\x12" + "3" を記述できます。 end note

String_Literalの型はstring

各文字列リテラルでは、必ずしも新しい文字列インスタンスが生成されるとは限りません。 文字列の等値演算子 (§12.12.8) に従って等価な 2 つ以上の文字列リテラルが同じアセンブリに出現する場合、これらの文字列リテラルは同じ文字列インスタンスを参照します。

: たとえば、

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

は、2 つのリテラルが同じ文字列インスタンスを参照するため、 True です。

end の例

6.4.5.7 null リテラル

null_literal
    : NULL
    ;

: null_literal は、新しいトークンの種類を導入しないため、パーサールールです。 end note

null_literalは、null値を表します。 型はありませんが、null リテラル変換 (§10.2.7) を使用して、任意の参照型または null 許容値型に変換できます。

6.4.6 演算子と区切り記号

いくつかの種類の演算子と区切り記号があります。 演算子は、1 つ以上のオペランドに関係する操作を記述するために、式で使用されます。

: 式 a + b は、 + 演算子を使用して、 abの 2 つのオペランドを追加します。 end の例

区切り記号は、グループ化と分離のためのものです。

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

right_shift
    : '>'  '>'
    ;

right_shift_assignment
    : '>' '>='
    ;

: right_shiftright_shift_assignment は、新しいトークンの種類を導入せず、2 つのトークンのシーケンスを表すパーサー ルールです。 operator_or_punctuatorルールは説明のみを目的として存在し、文法の他の場所では使用されません。 end note

right_shift は、 >>の 2 つのトークンで構成されます。 同様に、 right_shift_assignment は、 >>=の 2 つのトークンで構成されます。 構文文法の他の運用環境とは異なり、これらの各運用環境の 2 つのトークン間には、任意の種類の文字 (空白文字も含まない) は使用できません。 これらの生産は、 type_parameter_lists の正しい処理を可能にするために特別に扱われます (§15.2.3)。

: C# にジェネリックを追加する前は、 >>>>= の両方が単一トークンでした。 ただし、ジェネリックの構文では、 < 文字と > 文字を使用して、型パラメーターと型引数を区切ります。 多くの場合、 List<Dictionary<string, int>>などの入れ子になった構築型を使用することが望ましいです。 プログラマに >> をスペースで区切る必要はなく、2 つの operator_or_punctuatorの定義が変更されました。 end note

6.5 プリプロセス ディレクティブ

6.5.1 全般

前処理ディレクティブは、コンパイル ユニットのセクションを条件付きでスキップし、エラーと警告の条件を報告し、ソース コードの個別の領域を説明し、null 許容コンテキストを設定する機能を提供します。

: "前処理ディレクティブ" という用語は、C および C++ プログラミング言語との一貫性のためにのみ使用されます。 C# では、個別の前処理手順はありません。前処理ディレクティブは字句解析フェーズの一部として処理されます。 end note

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
    ;

:

  • プリプロセッサ文法では、すべての前処理ディレクティブに使用 PP_Directive 1 つの字句トークンが定義されています。 各前処理ディレクティブのセマンティクスは、この言語仕様で定義されていますが、実装方法ではありません。
  • PP_Startフラグメントは、行の先頭でのみ認識する必要があります。上記の ANTLR 字句述語getCharPositionInLine() == 0は、これを実現する方法の 1 つを示唆しており、有益な実装では別の戦略を使用できます。

end note

次の前処理ディレクティブを使用できます。

  • #define および #undef。これは、それぞれ条件付きコンパイル シンボル (§6.5.4) を定義および未定義にするために使用されます。
  • #if#elif#else、および #endif。これは、ソース コードの条件付きセクションをスキップするために使用されます (§6.5.5)。
  • #lineは、エラーと警告に対して出力される行番号を制御するために使用されます (§6.5.8)。
  • #errorは、エラーの発行に使用されます (§6.5.6)。
  • #region#endregion。ソース コードのセクションを明示的にマークするために使用されます (§6.5.7)。
  • #nullable: null 許容コンテキスト (§6.5.9) を指定するために使用されます。
  • #pragmaは、省略可能なコンテキスト情報をコンパイラに指定するために使用されます (§6.5.10)。

前処理ディレクティブは、常に別のソース コード行を占有し、常に # 文字と前処理ディレクティブ名で始まります。 空白は、 # 文字の前、および # 文字とディレクティブ名の間で行われる場合があります。

#define#undef#if#elif#else#endif#line#endregion、または#nullableディレクティブを含むソース行は、単一行コメントで終わる場合があります。 事前処理ディレクティブを含むソース行では、区切りコメント (コメントの /* */ スタイル) は使用できません。

前処理ディレクティブは、C# の構文文法の一部ではありません。 ただし、前処理ディレクティブを使用してトークンのシーケンスを含めたり除外したりすることができ、その方法で C# プログラムの意味に影響を与えることができます。

: コンパイル時に、プログラム

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

結果は、プログラムとまったく同じトークンシーケンスになります

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

したがって、字句的には、2 つのプログラムはまったく異なり、構文的には同じです。

end の例

6.5.2 条件付きコンパイル シンボル

#if#elif#else、および#endifディレクティブによって提供される条件付きコンパイル機能は、前処理式 (§6.5.3) と条件付きコンパイル シンボルによって制御されます。

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

実装で許容される Basic_Identifier 値に制限を適用する方法は実装の問題です。 end note

2 つの条件付きコンパイル シンボルは、次の変換が適用された後で同じ場合は同じと見なされます。

  • Unicode_Escape_Sequence は、対応する Unicode 文字に変換されます。
  • すべての Formatting_Characters が削除されます。

条件付きコンパイル シンボルには、 defined または undefined の 2 つの状態があります。 コンパイル ユニットの字句処理の開始時、条件付きコンパイル シンボルは、外部メカニズム (コマンド ライン コンパイラ オプションなど) によって明示的に定義されていない限り、未定義になります。 #define ディレクティブが処理されると、そのディレクティブで指定された条件付きコンパイル シンボルがそのコンパイル 単位で定義されます。 シンボルは、同じシンボルの #undef ディレクティブが処理されるまで、またはコンパイル ユニットの終了まで定義されたままになります。 これは、1 つのコンパイル ユニット内の #define ディレクティブと #undef ディレクティブが、同じプログラム内の他のコンパイル ユニットに影響を与えないことを意味します。

前処理式 (§6.5.3) で参照されている場合、定義済みの条件付きコンパイル シンボルにはブール値 trueがあり、未定義の条件付きコンパイル シンボルにはブール値 false。 条件付きコンパイル シンボルを事前処理式で参照する前に明示的に宣言する必要はありません。 代わりに、宣言されていないシンボルは単純に未定義であるため、値 false

条件付きコンパイル シンボルの名前空間は異なり、C# プログラム内の他のすべての名前付きエンティティとは別です。 条件付きコンパイル シンボルは、 #define ディレクティブと #undef ディレクティブ、および前処理式でのみ参照できます。

6.5.3 式の前処理

前処理式は、 #if ディレクティブと #elif ディレクティブで発生する可能性があります。 演算子 ! (プレフィックス論理否定のみ)、 ==!=&&、および || は前処理式で許可され、かっこはグループ化に使用できます。

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

前処理式で参照される場合、定義済みの条件付きコンパイル シンボルにはブール値 trueがあり、未定義の条件付きコンパイル シンボルにはブール値 false

前処理式の評価では、常にブール値が生成されます。 前処理式の評価規則は、定数式 (§12.23) の場合と同じですが、参照できるユーザー定義エンティティは条件付きコンパイル シンボルのみです。

6.5.4 定義ディレクティブ

定義ディレクティブは、条件付きコンパイル シンボルを定義または未定義にするために使用されます。

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

#define ディレクティブの処理により、指定された条件付きコンパイル シンボルが定義され、ディレクティブの後のソース行から始まります。 同様に、 #undef ディレクティブを処理すると、指定された条件付きコンパイル シンボルが未定義になり、ディレクティブの後のソース行から始まります。

コンパイル 単位内の #define ディレクティブと #undef ディレクティブは、コンパイル ユニット内の最初の token (§6.4) の前に発生する必要があります。それ以外の場合は、コンパイル時エラーが発生します。 直感的に言うと、 #define ディレクティブと #undef ディレクティブは、コンパイル ユニット内の "実際のコード" の前に置く必要があります。

: 例:

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

は有効です。 #define ディレクティブは、コンパイル ユニットの最初のトークン ( namespace キーワード) の前にあるためです。

end の例

: 次の例では、#define が実際のコードに従うため、コンパイル時エラーが発生します。

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

end の例

#defineは、既に定義されている条件付きコンパイル シンボルを定義できます。そのシンボルに対する#undefが介在することはありません。

: 次の例では、条件付きコンパイル シンボル A を定義し、再度定義します。

#define A
#define A

条件付きコンパイル シンボルをコンパイル オプションとして定義できるコンパイラの場合、このような再定義を行う別の方法は、ソースと同様にコンパイラ オプションとしてシンボルを定義することです。

end の例

#undefは、定義されていない条件付きコンパイル シンボルを "未定義" にする可能性があります。

: 次の例では、条件付きコンパイル シンボル A を定義し、2 回未定義にします。2 つ目の #undef は無効ですが、有効なままです。

#define A
#undef A
#undef A

end の例

6.5.5 条件付きコンパイル ディレクティブ

条件付きコンパイル ディレクティブは、コンパイル ユニットの一部を条件付きで含めたり除外したりするために使用されます。

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

条件付きコンパイル ディレクティブは、 #if ディレクティブ、0 個以上の #elif ディレクティブ、0 個または 1 個の #else ディレクティブ、および #endif ディレクティブで構成されるグループで記述する必要があります。 ディレクティブ間には、ソース コードの 条件セクション があります。 各セクションは、直前のディレクティブによって制御されます。 これらのディレクティブが完全なグループを形成する場合、条件付きセクション自体に入れ子になった条件付きコンパイル ディレクティブが含まれる場合があります。

通常の字句処理では、最大で 1 つの条件付きセクションが選択されます。

  • ディレクティブと#if ディレクティブの#elifは、trueが生成されるまで順番に評価されます。 式が trueを生成する場合は、対応するディレクティブの後の条件セクションが選択されます。
  • すべての PP_Expressionfalseが生成され、 #else ディレクティブが存在する場合は、 #else ディレクティブの後の条件付きセクションが選択されます。
  • それ以外の場合、条件付きセクションは選択されません。

選択した条件セクションがある場合は、通常の input_sectionとして処理されます。セクションに含まれるソース コードは字句文法に従う必要があります。トークンはセクションのソース コードから生成され、セクションの前処理ディレクティブには規定の効果があります。

残りの条件付きセクションはスキップされ、前処理ディレクティブを除くトークンはソース コードから生成されません。 したがって、プリプロセス ディレクティブを除くスキップされたソース コードは、字句的に正しくない可能性があります。 スキップされた前処理ディレクティブは字句的に正しい必要がありますが、それ以外の場合は処理されません。 スキップされる条件付きセクション内では、入れ子になった条件セクション (入れ子になった #if...#endif コンストラクトに含まれる) もスキップされます。

: 上記の文法では、前処理ディレクティブ間の条件付きセクションが構文的に正しくない可能性がある許容量はキャプチャされません。 したがって、文法は構文的に正しい入力のみをサポートするため、ANTLR 対応ではありません。 end note

: 次の例は、条件付きコンパイル ディレクティブを入れ子にする方法を示しています。

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

前処理ディレクティブを除き、スキップされたソース コードは字句分析の対象になりません。 たとえば、次の例は、 #else セクションの未終了のコメントにもかかわらず有効です。

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

ただし、プリプロセス ディレクティブは、ソース コードのスキップされたセクションでも字句的に正しい必要があることに注意してください。

前処理ディレクティブは、複数行の入力要素内に出現しても処理されません。 たとえば、プログラムは次のようになります。

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

結果として出力されます。

hello,
#if Debug
        world
#else
        Nebraska
#endif

特異なケースでは、処理される前処理ディレクティブのセットは、 pp_expressionの評価に依存する場合があります。 この例では、次の処理を実行します。

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

classが定義されているかどうかに関係なく、常に同じトークン ストリーム (Q{}X) が生成されます。 Xが定義されている場合は、マルチ行コメントのため、処理されたディレクティブのみが#ifされ、#endifされます。 Xが未定義の場合、3 つのディレクティブ (#if#else#endif) がディレクティブ セットの一部になります。

end の例

6.5.6 診断ディレクティブ

診断ディレクティブは、他のコンパイル時のエラーや警告と同じ方法で報告される明示的なエラーメッセージと警告メッセージを生成するために使用されます。

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

fragment PP_Message
    : PP_Whitespace Input_Character*
    ;

: 例

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

は、条件付きコンパイル シンボル DebugRetail の両方が定義されている場合、コンパイル時エラー ("ビルドをデバッグとリテールの両方にすることはできません") を生成します。 PP_Messageには任意のテキストを含めることができます。具体的には、can'tという単語の一重引用符で示すように、整形式のトークンを含める必要はありません。

end の例

6.5.7 Region ディレクティブ

リージョン ディレクティブは、ソース コードの明示的なリージョンをマークするために使用されます。

fragment PP_Region
    : PP_Start_Region
    | PP_End_Region
    ;

fragment PP_Start_Region
    : 'region' PP_Message?
    ;

fragment PP_End_Region
    : 'endregion' PP_Message?
    ;

セマンティックの意味はリージョンに関連付けされません。領域は、プログラマまたはソース コードのセクションをマークする自動化ツールによって使用することを目的としています。 すべての#endregion ディレクティブに一致する 1 つの#region ディレクティブが存在する必要があります。 同様に、 #region ディレクティブまたは #endregion ディレクティブで指定されたメッセージはセマンティックな意味を持っていません。単に領域を識別するのに役立ちます。 一致する #region ディレクティブと #endregion ディレクティブの PP_Messageが異なる場合があります。

領域の字句処理:

#region
...
#endregion

は、次の形式の条件付きコンパイル ディレクティブの字句処理に正確に対応します。

#if true
...
#endif

: これは、リージョンに 1 つ以上の #if/.../#endifを含めたり、 #if/.../#endif 内の条件セクションに含めたりすることはできますが、リージョンが #if/.../#endifの一部と重複したり、異なる条件付きセクションで開始 > を終了したりできないことを意味します。 end note

6.5.8 Line ディレクティブ

行ディレクティブは、警告やエラーなどの出力でコンパイラによって報告される行番号とコンパイル 単位名を変更するために使用できます。 これらの値は、呼び出し元情報属性 (§22.5.6) でも使用されます。

: 行ディレクティブは、他のテキスト入力から C# ソース コードを生成するメタ プログラミング ツールで最もよく使用されます。 end note

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

#line ディレクティブが存在しない場合、コンパイラは出力に真の行番号とコンパイルユニット名を報告します。 されていない PP_Line_Indicator を含む ディレクティブを処理する場合、コンパイラはディレクティブを した後の行 を、指定された行番号 (および指定されている場合はコンパイル単位名) として扱います。

Decimal_Digit+に許可される最大値は実装定義です。

#line default ディレクティブは、上記のすべての#line ディレクティブの効果を元に戻します。 コンパイラは、#line ディレクティブが処理されていないかのように、後続の行の真の行情報を報告します。

#line hidden ディレクティブは、エラー メッセージで報告されるコンパイル単位と行番号に影響を与えたり、CallerLineNumberAttribute (§22.5.6.2) を使用して生成されたりしません。 これは、デバッグ時に、 #line hidden ディレクティブと後続の #line ディレクティブ ( #line hiddenではない) の間のすべての行に行番号情報が含まれていないため、ソース レベルのデバッグ ツールに影響を与えることを目的としています。コードをステップ実行するときに完全にスキップされます。

: PP_Compilation_Unit_Name にはエスケープ シーケンスのようなテキストが含まれている場合がありますが、このようなテキストはエスケープ シーケンスではありません。このコンテキストでは、"\" 文字は通常の円記号を指定するだけです。 end note

6.5.9 Nullable ディレクティブ

null 許容ディレクティブは、以下で説明するように、null 許容コンテキストを制御します。

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

null 許容ディレクティブは、後続のコード行に使用可能なフラグを、別の null 許容ディレクティブによってオーバーライドされるまで、または コンパイル_unit の終了まで設定します。 null 許容コンテキストには、 annotationswarnings の 2 つのフラグが含まれています。 null 許容ディレクティブの各形式の効果は次のとおりです。

  • #nullable disable: null 許容注釈と null 許容警告フラグの両方を無効にします。
  • #nullable enable: null 許容注釈と null 許容警告フラグの両方を有効にします。
  • #nullable restore: 注釈フラグと警告フラグの両方を、外部メカニズムで指定された状態 (存在する場合) に復元します。
  • #nullable disable annotations: null 許容注釈フラグを無効にします。 null 許容警告フラグは影響を受けません。
  • #nullable enable annotations: null 許容注釈フラグを有効にします。 null 許容警告フラグは影響を受けません。
  • #nullable restore annotations: null 許容注釈フラグを、外部メカニズムで指定された状態 (存在する場合) に復元します。 null 許容警告フラグは影響を受けません。
  • #nullable disable warnings: null 許容警告フラグを無効にします。 null 許容注釈フラグは影響を受けません。
  • #nullable enable warnings: null 許容警告フラグを有効にします。 null 許容注釈フラグは影響を受けません。
  • #nullable restore warnings: null 許容警告フラグを、外部メカニズムで指定された状態 (存在する場合) に復元します。 null 許容注釈フラグは影響を受けません。

式の null 許容状態は常に追跡されます。 注釈フラグの状態と、null 許容注釈の有無 ( ?) によって、変数宣言の初期 null 状態が決まります。 警告は、警告フラグが有効になっている場合にのみ発行されます。

: 例

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

は、コンパイル時の警告を生成します (" xnull")。 xの null 許容状態は、どこでも追跡されます。 警告フラグが有効になっていると、警告が発行されます。

end の例

6.5.10 Pragma ディレクティブ

#pragma前処理ディレクティブは、コンパイラにコンテキスト情報を指定するために使用されます。

: たとえば、コンパイラは、 #pragma ディレクティブを提供する場合があります。

  • 後続のコードをコンパイルするときに、特定の警告メッセージを有効または無効にします。
  • 後続のコードに適用する最適化を指定します。
  • デバッガーで使用する情報を指定します。

end note

fragment PP_Pragma
    : 'pragma' PP_Pragma_Text?
    ;

fragment PP_Pragma_Text
    : PP_Whitespace Input_Character*
    ;

PP_Pragma_TextInput_Characterは、実装で定義された方法でコンパイラによって解釈されます。 #pragma ディレクティブで指定された情報は、プログラムのセマンティクスを変更しません。 #pragma ディレクティブは、この言語仕様の範囲外のコンパイラの動作のみを変更します。 コンパイラが Input_Characterを解釈できない場合、コンパイラは警告を生成できます。ただし、コンパイル時エラーは生成されません。

: PP_Pragma_Text には任意のテキストを含めることができます。具体的には、整形式のトークンを含める必要はありません。 end note