共用方式為


6 語彙結構

6.1 程式

C# 程式是由一或多個原始程式 檔所組成,正式稱為 編譯單位~14.2)。 雖然編譯單位可能與文件系統中的檔案有一對一對應,但不需要這類對應。

從概念上講,程式會使用三個步驟來編譯:

  1. 轉換,可將檔案從特定字元轉譯和編碼配置轉換成 Unicode 字元序列。
  2. 語彙分析,其會將 Unicode 輸入字串流轉譯成令牌數據流。
  3. 語法分析,其會將令牌數據流轉譯為可執行的程序代碼。

符合規範的實作應接受以 UTF-8 編碼形式編碼的 Unicode 編譯單位(如 Unicode 標準所定義),並將其轉換成 Unicode 字元序列。 實作可以選擇接受和轉換其他字元編碼配置(例如 UTF-16、UTF-32 或非 Unicode 字元對應)。

注意:Unicode NULL 字元 (U+0000) 的處理是實作定義的。 基於可移植性和可讀性,強烈建議開發人員避免在原始程式碼中使用這個字元。 當字元或字串常值內需要字元時,可以改用逸出序列 \0\u0000end note

注意:此規格的範圍超出此規格,可定義使用 Unicode 以外的字元表示法的檔案如何轉換成 Unicode 字元序列。 不過,在這類轉換期間,建議將其他字元集中的一般分隔字元(或序列)轉譯為由 Unicode 歸位字元 (U+000D) 組成的雙字元序列,後面接著 Unicode 換行字元 (U+000A)。 在大多數情況下,此轉換將沒有任何可見的效果;不過,這會影響逐字字串常值標記的解譯({6.4.5.6)。 這項建議的目的是在支援不同非 Unicode 字元集的系統之間移動時,允許逐字字串常值產生相同的字元序列,特別是使用不同字元序列進行行分隔的系統。 end note

6.2 文法

6.2.1 一般

此規格會使用兩種文法呈現 C# 程式設計語言的語法。 語彙文法{6.2.3) 會定義 Unicode 字元如何結合成行終止符、空格符、批註、標記和前置處理指示詞。 語法文法{6.2.4) 定義如何結合語匯文法產生的標記,以形成 C# 程式。

所有終端字元都會被理解為從U+0020到U+007F範圍的適當 Unicode 字元,而不是其他 Unicode 字元範圍中的任何類似字元。

6.2.2 文法表示法

語彙和語法文法會以 ANTLR 文法工具的 Extended Backus-Naur 形式呈現。

使用 ANTLR 表示法時,此規格不會提供 C# 的完整 ANTLR 就緒「參考文法」;手動或使用 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# 程式中的每一個編譯單位都應該符合 語彙文法的輸入 生產(~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_access12.8.7)的製作可能會對表達式的文法產生模棱兩可。

範例:語句:

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

可以解譯為具有兩個自變數的呼叫 FG < A 以及 B > (7)。 或者,它可以解譯為使用一個自變數的呼叫 F ,這是對具有兩個型別自變數和一個一般自變數之泛型方法 G 的呼叫。

end 範例

如果一連串的令牌可以剖析為simple_name({12.8.4)、member_access({12.8.7>

  • 其中一個 ( ) ] } : ; , . ? == != | ^ && || & [; 或
  • 其中一個關係運算符 < <= >= is as; 或
  • 出現在查詢表達式內的內容查詢關鍵詞;或
  • 在某些內容中, 標識碼 會被視為釐清的令牌。 這些內容是在剖析 Tuple 常值的第一個元素時,會緊接其中一個關鍵詞 、 或 ,或在剖析 Tuple 常值的第一個專案時,剖析標記的順序會緊接在解譯的標記序列之前is,而標識符後面接著 caseTuple 常值的專案。out(:,

如果下列令牌在此清單中,或這類內容中的標識符,則會將type_argument_list保留為simple_name的一部分member_accesspointer_member存取,並捨棄任何其他可能剖析令牌序列的可能剖析。 否則,type_argument_list不會被視為simple_namemember_accesspointer_member_access的一部分,即使沒有其他可能的令牌序列剖析也一樣。

注意:剖析namespace_or_type_name中type_argument_list,不會套用這些規則(7.8)。 end note

範例:語句:

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

會根據此規則將 解譯為具有一個自變數的 F 呼叫,這是對具有兩個型別自變數和一個一般自變數之泛型方法 G 的呼叫。 語句

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

將會以兩個自變數來解譯為的 F 呼叫。 陳述式

x = F<A> + y;

將會解譯為小於運算符、大於運算符和一元加運算元,就像已寫入 x = (F < A) > (+y)語句一樣,而不是以type_argument_list後面接著二元加運算子的simple_name。 在語句中

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

令牌C<T>會解譯為具有type_argument_list的namespace_or_type_name,因為type_argument_list之後&&存在釐清令牌

表達式 (A < B, C > D) 是具有兩個元素的元組,每個元素都是比較。

表達式 (A<B,C> D, E) 是具有兩個元素的元組,其中第一個是宣告表達式。

M(A < B, C > D, E)調用有三個自變數。

M(out A<B,C> D, E)調用有兩個自變數,其中第一個out自變數是宣告。

表達式 e is A<B> C 會使用宣告模式。

案例標籤 case A<B> C: 會使用宣告模式。

end 範例

如果 relational_expression「relational_expressionis類型」替代方案。

6.3 語彙分析

6.3.1 一般

為了方便起見,語彙文法會定義並參考下列具名詞典標記:

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

雖然這些是詞彙規則,但這些名稱會以全大寫字母拼字,以區分它們與一般語彙規則名稱。

注意:這些便利性規則是一般做法的例外,即不會為常值字串所定義的令牌提供明確的令牌名稱。 end note

輸入生產環境會定義 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# 編譯單位的語彙結構:行終止符 ({6.3.2)、空格符 ({6.3.4)、批注 ({6.3.3)、標記 ({6.4)和前置處理指示詞 ({6.5)。 在這些基本元素中,只有標記在 C# 程式的語法文法中很重要({6.2.4)。

C# 編譯單位的語彙處理包含將檔案縮減為令牌序列,成為語法分析的輸入。 行終止符、空格符和批註可以用於個別標記,而前置處理指示詞可能會導致略過編譯單位的區段,但否則這些語彙元素不會影響 C# 程式的語法結構。

當數個語彙文法生產專案符合編譯單位中的字元序列時,語彙處理一律會形成最長的語匯元素。

範例:字元序列 // 會當做單行批註的開頭處理,因為該語匯專案比單 / 一標記長。 end 範例

某些令牌是由一組語彙規則所定義;主要規則和一或多個子規則。 後者在文法中標示為 , 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+0085)、行分隔符(U+2028)或段落分隔符(U+2029)。

注意:額外的歸位字元可讓程式以沒有終止New_Line的PP_Directive\6.5) 結尾。 end note

6.3.3 批注

支援兩種形式的批注:分隔的批注和單行批注。

分隔的批註以字元/*開頭,並以字元 */結尾。 分隔的批註可以佔用一行、單行或多行的一部分。

範例:範例

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

包含分隔註解。

end 範例

單行批注會以字元//開頭,並延伸至行尾。

範例:範例

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

註解不會巢狀化。 /**/ 的字元序列在單行註解內不具特殊意義,且 ///* 的字元序列在分隔註解內不具特殊意義。

批註不會在字元和字串常值內處理。

注意:這些規則必須仔細解譯。 例如,在下列範例中,在和 A之間B結束之前C()開始的分隔批注。 原因是

// B */ C();

實際上不是單行批注,因為 // 分隔批注內沒有特殊意義,因此 */ 在該行中具有一般的特殊意義。

同樣地,在 之前 D 開始的分隔批注會在 之前 E結束。 原因是不是 "D */ " 字串常值,因為初始雙引號字元會出現在分隔批注內。

單行批注內並沒有任何特殊意義的結果/**/,就是可以在每一行開頭加上//一組原始程式碼行來加上批注。 一般而言,它無法放在 /* 這些行之前和 */ 之後,因為這不會正確地封裝區塊中的分隔批注,而且通常可能會完全變更這類分隔批注的結構。

範例程式碼:

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

end note

Single_Line_Comment 和具有特定格式的 Delimited_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” 字元之後的十六進位數位所構成的單一 Unicode 字碼點。 由於 C# 使用字元和字串值中 Unicode 字碼點的 16 位編碼,因此範圍U+10000U+10FFFF中的 Unicode 字碼點會使用兩個 Unicode 代理程式代碼單位來表示。 字元常值中不允許上述 U+FFFF Unicode 字碼點。 上述 U+10FFFF Unicode 字碼點無效,且不受支援。

不會執行多個翻譯。 例如,字串常值 "\u005Cu005C" 相當於 "\u005C" ,而不是 "\"

注意:Unicode 值 \u005C 是字元 “\ ”。 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 與關鍵詞或內容關鍵詞不符,因為這些關鍵詞前面會有語彙規則。
  • 片段 Escaped_Identifier 包含逸出關鍵詞和內容關鍵詞,因為它們是開頭較長標記的一 @ 部分,而且語彙處理一律會形成最長的語彙元素({6.3.1)。
  • 實作如何強制執行允許 Unicode_Escape_Sequence 值的限制是實作問題。

end note

範例:有效識別碼的範例包括 identifier1_identifier2@ifend 範例

符合程式中的標識碼應採用 Unicode 標準化表單 C 所定義的標準格式,如 Unicode 標準附錄 15 所定義。 遇到非正規化表單 C 中的標識符時的行為是實作定義;不過,不需要診斷。

前置詞 “@” 可讓您使用關鍵詞作為標識符,這在與其他程式設計語言交集時很有用。 字元 @ 實際上不是標識符的一部分,因此標識符可能以其他語言視為一般標識碼,而沒有前置詞。 具有前置詞的 @ 標識碼稱為 逐字標識碼

注意:允許對不是關鍵詞的標識碼使用 @ 前置詞,但強烈建議不要做為樣式問題。 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 範例

如果套用下列轉換之後,兩個標識符會視為相同,順序如下:

  • 如果已使用,則會移除前置詞 “@
  • 每個 Unicode_Escape_Sequence 都會轉換成其對應的 Unicode 字元。
  • 拿掉任何 Formatting_Character

名為 _ 的標識碼語意取決於其出現的內容:

  • 它可以表示具名的程序專案,例如變數、類別或方法,或
  • 它可以表示丟棄(§9.2.9.2)。

包含兩個連續底線字元的標識碼 (U+005F) 會保留供實作使用;不過,如果定義這類標識符,則不需要診斷。

注意:例如,實作可能會提供以兩個底線開頭的擴充關鍵詞。 end note

6.4.4 關鍵詞

關鍵詞是保留的類似標識碼的字元序列,除了字元前面@加上時,不能當做標識碼使用。

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

注意:規則 關鍵詞contextual_keyword 是剖析器規則,因為它們不會引進新的令牌種類。 所有關鍵詞和內容關鍵詞都是由隱含語彙規則所定義,因為它們會在文法中以常值字串的形式出現(~6.2.3)。 end note

在大部分情況下,內容關鍵詞的語法位置使得它們永遠無法與一般標識碼用法混淆。 例如,在屬性宣告內, getset 標識符具有特殊意義({15.7.3)。 這些 get 位置中不允許或 set 以外的標識碼,因此此用法不會與使用這些字組做為標識碼的用法衝突。

在某些情況下,文法不足以區分內容關鍵詞使用方式與標識符。 在所有這類情況下,都會指定如何釐清這兩者之間的混淆。 例如,隱含型別局部變數宣告中的內容關鍵詞 var{13.6.2)可能會與稱為 var的宣告類型發生衝突,在此情況下,宣告的名稱優先於使用標識符做為內容關鍵詞。

另一個這類釐清的範例是內容關鍵詞 await•12.9.8.1),只有在宣告 async的方法內,但可作為其他地方的標識符時,才會被視為關鍵詞。

就像關鍵詞一樣,內容關鍵詞可以做為一般標識符,方法是在前面加上 @ 字元。

注意:當做內容關鍵詞使用時,這些標識碼不能包含 Unicode_Escape_Sequenceend note

6.4.5 常值

6.4.5.1 一般

~12.8.2) 是值的原始程式碼表示法。

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

注意常值 是剖析器規則,因為它會將其他令牌類型分組,而且不會引入新的令牌種類。 end note

6.4.5.2 布爾常值

有兩個布爾常值: truefalse

boolean_literal
    : TRUE
    | FALSE
    ;

注意boolean_literal 是剖析器規則,因為它會將其他令牌類型分組,而且不會引入新的令牌種類。 end note

boolean_literal類型bool

6.4.5.3 整數常值

整數常值可用來寫入、intuintlong類型的ulong值。 整數常值有三種可能的形式:十進位、十六進位和二進位。

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

整數常值的型別決定如下:

  • 如果常值沒有後置詞,其具有可表示其值的第一種類型:int、、uintlongulong
  • 如果 常值由 Uu後置詞,則具有可表示其值的第一種類型: uintulong
  • 如果 常值由 Ll後置詞,則具有可表示其值的第一種類型: longulong
  • 如果常值後綴為 、、、、、、、、 或 UL,則其類型Ul為 。 uLulLULulUluulong

如果整數常值所表示的值超出型別的範圍 ulong ,就會發生編譯時期錯誤。

注意:就樣式而言,建議在撰寫類型的L常值時使用 “l” 而不是 “long”,因為很容易混淆字母 “l” 與數位 “1”。 end note

若要允許最小的可能 intlong 值寫入為整數常值,下列兩個規則存在:

  • 當Integer_Literal代表值 2147483648 (2ーー) 且沒有Integer_Type_Suffix緊接在一元減號運算符標記之後的標記出現時({12.9.3),結果 (兩個標記) 都是 int 的常數,其值−2147483648為 int 的常數。。 在所有其他情況下,這類 Integer_Literal 的類型為 uint
  • 當 Integer_Literal代表值 (2⁶ー) 且沒有Integer_Type_Suffix或Integer_Type_Suffix9223372036854775808緊接在一元減號運算符標記之後的標記 ({12.9.3l類型的常數 ({2⁶ー)。long−9223372036854775808 在所有其他情況下,這類 Integer_Literal 的類型為 ulong

範例:

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 都是 型別 floatend 範例

  • 由或 D 後綴d為 的實際常值,類型double為 。

    範例:常值 1d1.5d1e10d123.456D 都是 型別 doubleend 範例

  • 由或 M 後綴m為 的實際常值,類型decimal為 。

    範例:常值 1m1.5m1e10m123.456M 都是 型別 decimalend 範例
    這個常值會藉由取得確切值來轉換成 decimal 值,並在必要時,使用銀行家捨入的捨入值,以四捨五入到最接近的可表示值(~8.3.8)。 除非值四捨五入,否則會保留常值中的任何明顯小數字數。 注意:因此,常值 2.900m 會剖析成 decimal 帶正負號 0、係數 2900和小數字 3數的 。 end note

如果指定常值的大小太大,無法以指定的型別表示,就會發生編譯時期錯誤。

注意:特別是 ,Real_Literal 永遠不會產生浮點無限大。 不過,非零 Real_Literal 可能會四捨五入為零。 end note

類型floatdouble的實際常值或值是由使用 IEC 60559「四捨五入至最接近」模式來決定,且系結中斷為「偶數」(具有最小有效位零的值),以及所有視為有效位數的數位。

注意:在實際常值中,小數點之後一律需要十進位數。 例如, 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 字元常值

字元常值代表單一字元,並以引號括住字元,如 中所示 '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?
    ;

注意:字元\反斜杠字元 () 後面的字元必須是下列其中一個字元:'、、"\0abfnrtuUxv 否則,會發生編譯時期錯誤。 end note

注意:使用\xHexadecimal_Escape_Sequence生產環境可能會容易出錯且難以讀取,因為 之後\x的十六進位數位數目可變。 例如,在程式代碼中:

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

它可能一開始會出現,前置字元在兩個字串中都是相同的 (U+0009,製表元)。 事實上,第二個字串會以 U+9BAD 開頭,因為 “Bad” 一詞中的所有三個字母都是有效的十六進位數位。 就風格而言,建議 \x 避免使用特定的逸出序列(\t 在此範例中為)或固定長度 \u 逸出序列。

end note

十六進位逸出序列代表單一 Unicode UTF-16 程式代碼單位,其值是由 「\x」 之後的十六進位數位所組成。

如果字元常值所表示的值大於 U+FFFF,就會發生編譯時期錯誤。

字元常值中的 Unicode 逸出序列 (~6.4.2) 應位於 的範圍U+0000U+FFFF中。

簡單的逸出序列代表 Unicode 字元,如下表所述。

逸出序列 字元名稱 Unicode 字碼點
\' 單引號 U+0027
\" 雙引號 U+0022
\\ 反斜線 U+005C
\0 Null U+0000
\a Alert U+0007
\b 退格鍵 U+0008
\f 換頁字元 U+000C
\n 新行 U+000A
\r 歸位字元 U+000D
\t 水平 Tab 鍵 U+0009
\v 垂直 Tab 鍵 U+000B

Character_Literal的類型char

6.4.5.6 字串常值

C# 支援兩種形式的字串常值:一般字串常值逐字字串常值。 一般字串常值包含以雙引弧括住的零個或多個字元,如 中的 "hello",而且可以包含簡單的逸出序列(例如 \t 製表元),以及十六進位和 Unicode 逸出序列。

逐字字串常值包含 @ 字元後面接著雙引號字元、零個或多個字元,以及結尾雙引號字元。

範例:簡單的範例是 @"hello"end 範例

在逐字字串常值中,分隔符之間的字元會逐字解譯,唯一 的例外狀況是Quote_Escape_Sequence,代表一個雙引號字元。 特別是,簡單的逸出序列和十六進位和 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

注意:由於十六進位逸出序列可以有可變數目的十六進位位數,字串常值 "\x123" 會包含具有十六進位值 123的單一字元。 若要建立包含十六進位值的 12 字元字串,後面接著字元 3,可以改為寫入 "\x00123""\x12" + "3"end note

String_Literal的類型string

每個字串常值不一定會導致新的字串實例。 當兩個或多個字串常值根據字串相等運算符 (^12.12.12.8) 出現在相同的元件中時,這些字串常值會參考相同的字串實例。

範例:例如,所產生的輸出

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

是因為 True 兩個常值參考相同的字串實例。

end 範例

6.4.5.7 Null 常值

null_literal
    : NULL
    ;

注意null_literal 是剖析器規則,因為它不會引進新的令牌種類。 end note

null_literal代表null值。 它沒有類型,但可以透過 Null 常值轉換轉換成任何參考型別或可為 Null 的實值型別(~10.2.7)。

6.4.6 運算符和標點符號

有數種運算子和標點符號。 運算子會在運算式中用來描述涉及一或多個運算元的作業。

範例:表示式 a + b 會使用 + 運算子來新增兩個操作數 abend 範例

標點符號用於分組和分隔。

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

right_shift
    : '>'  '>'
    ;

right_shift_assignment
    : '>' '>='
    ;

注意right_shiftright_shift_assignment 是剖析器規則,因為它們不會引進新的令牌種類,但代表兩個令牌的序列。 operator_or_punctuator規則僅適用於描述性用途,而且不會在文法中其他地方使用。 end note

right_shift是由兩個令牌>>所組成。 同樣地, right_shift_assignment 是由兩個令牌 >>=所組成。 不同於語法文法中的其他生產專案,每個製作專案中的兩個標記之間都不允許任何種類的字元(甚至沒有空格符)。 這些生產會特別處理,以便正確處理 type_parameter_lists)。15.2.3

注意:在將泛型新增至 C# 之前, >>>>= 都是單一令牌。 不過,泛型的語法會使用 <> 字元來分隔型別參數和型別自變數。 通常最好使用巢狀建構型別,例如 List<Dictionary<string, int>>。 不需要程式設計人員以空格分隔 >> ,而是變更了兩 個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 。 每個前置處理指示詞的語意都是在此語言規格中定義,但無法實作它們。
  • 上述 PP_Start ANTLR 語彙述詞只有在一行開頭才能夠辨識片段,getCharPositionInLine() == 0這表示有一種方式可以達成,而且只提供資訊,實作可能會使用不同的策略。

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() {}
}

因此,雖然語彙上,這兩個程式在語法上是完全相同的。

end 範例

6.5.2 條件式編譯符號

、、 和 指示詞所提供的#if條件式編譯功能是透過前置處理運算式 (#elif) 和條件式編譯符號來控制。#else#endif

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

注意 實作對允許 Basic_Identifier 值強制執行限制的方式是實作問題。 end note

套用下列轉換之後,如果兩個條件式編譯符號在套用下列轉換之後相同,則會視為相同:

  • 每個 Unicode_Escape_Sequence 都會轉換成其對應的 Unicode 字元。
  • 拿掉任何 Formatting_Characters

條件式編譯符號有兩種可能的狀態:已定義未定義。 在編譯單位的語彙處理開始時,除非外部機制明確定義條件式編譯符號,否則條件式編譯符號是未定義的(例如命令行編譯程式選項)。 #define處理指示詞時,該指示詞中名為的條件式編譯符號就會在該編譯單位中定義。 符號會維持定義,直到 #undef 處理該相同符號的指示詞,或直到到達編譯單位的結尾為止。 其中 #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 指示詞應該發生在編譯單位中的第一 個標記~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 ,然後取消定義兩次;雖然第二 #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 詞、零或多個 #elif 指示詞、零或一 #else#endif 指示詞和 指示詞組成的群組撰寫。 指示詞之間的是原始程式碼的條件區段 每個區段都是由緊接在前面的 指示詞所控制。 條件式區段本身可能包含巢狀條件式編譯指示詞,前提是這些指示詞會形成完整的群組。

最多會選取其中一個包含的條件區段來進行一般語匯處理:

  • #elif會依序評估,直到產生 true為止。 如果表達式產生 true,則會選取對應指示詞後面的條件區段。
  • 如果所有#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

不論是否已定義,一律會產生相同的權杖資料流 (classQ{}X 如果 X 已定義,則唯一處理過的指示詞是 #if#endif,因為多行批注。 如果 X 未定義,則三個指示詞 (#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 相符 #region 的指示詞。 或 #region 指示詞中指定的#endregion訊息同樣沒有語意意義;它只會用來識別區域。 比 #region 對和 #endregion 指示詞可能有不同的 PP_Message

區域的語彙處理:

#region
...
#endregion

對應至表單之條件式編譯指示詞的語彙處理:

#if true
...
#endif

注意:這表示區域可以包含一或多個 #if/.../#endif,或包含在 /.../#if; #endif但區域不能只與 /.../#if#endif一部分重疊,或在不同的條件區段中開始和結束。 end note

6.5.8 行指示詞

行指示詞可用來改變編譯程式在輸出中回報的行號和編譯單位名稱,例如警告和錯誤。 呼叫端資訊屬性也會使用這些值(~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 可為 Null 指示詞

可為 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 的內容包含兩個旗標: 註釋警告。 每種可為 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);

會產生編譯時期警告 (“as is xnull” )。 的可為 Null 狀態 x 會隨處追蹤。 啟用警告旗標時,就會發出警告。

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_Text 中的 Input_Character會以實作定義的方式由編譯程式解譯。 指示詞中 #pragma 提供的資訊不得變更程式語意。 指示 #pragma 詞只能變更超出此語言規格範圍的編譯程序行為。 如果編譯程式無法解譯 Input_Characters,編譯程式可能會產生警告;不過,它不會產生編譯時間錯誤。

注意PP_Pragma_Text 可以包含任意文字;具體而言,它不需要包含格式正確的標記。 end note