C#语言规范(2.5 预处理指令) (转摘)

5 预处理指令

预处理指令提供按条件跳过源文件中的节、报告错误和警告条件以及描绘源代码的不同区域的能力。使用术语“预处理指令”只是为了与 C 和 C++ 编程语言保持一致。在 C# 中没有单独的预处理步骤;预处理指令按词法分析阶段的一部分处理。

pp-directive:pp 指令:)

pp-declarationpp 声明
pp-conditionalpp 条件
pp-linepp
pp-diagnosticpp 诊断
pp-regionpp 区域

下面是可用的预处理指令

·          #define #undef分别用于定义和取消定义条件编译符号 2.5.3

·          #if#elif#else #endif用于按条件跳过源代码中的节 2.5.4

·          #line用于控制行号在发布错误和警告信息时使用)( 2.5.7

·          #error #warning分别用于发出错误和警告 2.5.5

·          #region #endregion用于显式标记源代码中的节 2.5.6

预处理指令总是占用源代码中的单独一行并且总是以 # 字符和预处理指令名称开头。# 字符的前面以及 # 字符与指令名称之间可以出现空白符。

包含 #define#undef#if#elif#else#endif #line 指令的源代码行可以用单行注释结束。在包含预处理指令的源行上不允许使用带分隔符的注释(/* */ 样式的注释)。

预处理指令既不是标记也不是 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.5.1 条件编译符号

#if#elif#else #endif 指令提供的条件编译功能是通过预处理表达式 2.5.1 和条件编译符号来控制的。

conditional-symbol:(条件符号:)

true false 外的任何标识符或关键字

条件编译符号有两种可能的状态已定义的或未定义的。在源文件词法处理开始时,条件编译符号除非已由外部机制(如命令行编译器选项)显式定义,否则是未定义的。当处理 #define 指令时,在指令中指定的条件编译符号在那个源文件中成为已定义的。此后,该符号就一直保持已定义的状态,直到处理一条关于同一符号的 #undef 指令,或者到达源文件的结尾。这意味着一个源文件中的 #define#undef 指令对同一程序中的其他源文件没有任何影响。

当在预处理表达式中引用时已定义的条件编译符号具有布尔值 true未定义的条件编译符号具有布尔值 false。不要求在预处理表达式中引用条件编译符号之前显式声明它们。相反,未声明的符号只是未定义的,因此具有值 false

条件编译符号的命名空间与 C# 程序中的所有其他命名实体截然不同。只能在 #define#undef 指令以及预处理表达式中引用条件编译符号。


2.5.2 预处理表达式

预处理表达式可以出现在 #if #elif 指令中。在预处理表达式中允许使用 !==!=&&|| 运算符,并且可以使用括号进行分组。

pp-expression:pp 表达式:)

whitespaceopt   pp-or-expression   whitespaceopt(空白可选   pp 或表达式   空白可选

pp-or-expression:pp 或表达式:)

pp-and-expressionpp 与表达式
pp-or-expression   whitespaceopt   ||   whitespaceopt   pp-and-expressionpp 或表达式   空白可选   ||   空白可选   pp 与表达式

pp-and-expression:pp 与表达式:)

pp-equality-expressionpp 相等表达式
pp-and-expression   whitespaceopt   &&   whitespaceopt   pp-equality-expressionpp 与表达式   空白可选   &&   空白可选   pp 相等表达式

pp-equality-expression:pp 相等表达式:)

pp-unary-expressionpp 一元表达式
pp-equality-expression   whitespaceopt   ==   whitespaceopt   pp-unary-expressionpp 相等表达式   空白可选   ==   空白可选   pp 一元表达式
pp-equality-expression   whitespaceopt   !=   whitespaceopt   pp-unary-expressionpp 相等表达式   空白可选   !=   空白可选   pp 一元表达式

pp-unary-expression:pp 一元表达式:)

pp-primary-expressionpp 基本表达式
!   whitespaceopt   pp-unary-expression!   空白可选   pp 一元表达式

pp-primary-expression:pp 基本表达式:)

true
false
conditional-symbol
条件符号

(   whitespaceopt   pp-expression   whitespaceopt   )((空白可选   pp 表达式   空白可选   )

当在预处理表达式中引用时已定义的条件编译符号具有布尔值 true未定义的条件编译符号具有布尔值 false

预处理表达式的计算总是产生一个布尔值。预处理表达式的计算规则与常数表达式 7.15 相同唯一的例外是在这里唯一可引用的用户定义实体是条件编译符号。


2.5.3 声明指令

声明指令用于定义或取消定义条件编译符号。

pp-declaration:pp 声明:)

whitespaceopt   #   whitespaceopt   define   whitespace   conditional-symbol   pp-new-line空白可选   #   空白可选   define   空白   条件符号   pp 新行
whitespaceopt   #   whitespaceopt   undef   whitespace   conditional-symbol   pp-new-line空白可选   #   空白可选   undef   空白   条件符号   pp 新行

pp-new-line:pp 新行:)

whitespaceopt   single-line-commentopt   new-line空白可选   单行注释可选   新行

#define 指令的处理使给定的条件编译符号成为已定义的从跟在指令后面的源代码行开始。类似地,对 #undef 指令的处理使给定的条件编译符号成为未定义的(从跟在指令后面的源代码行开始)。

源文件中的任何 #define #undef 指令都必须出现在源文件中第一个标记”( 2.4 的前面否则将发生编译时错误。直观地讲,#define#undef 指令必须位于源文件中所有“实代码”的前面。

示例

#define Enterprise

#if Professional || Enterprise

    #define Advanced

#endif

namespace Megacorp.Data

{

    #if Advanced

    class PivotTable {...}

    #endif

}

是有效的这是因为 #define 指令位于源文件中第一个标记namespace 关键字的前面。

下面的示例产生编译时错误因为 #define 指令在实代码后面出现

#define A

namespace N

{

    #define B

    #if B

    class Class1 {}

    #endif

}

#define 指令可用于重复地定义一个已定义的条件编译符号而不必对该符号插入任何 #undef。下面的示例定义一个条件编译符号 A,然后再次定义它。

#define A

#define A

#undef 指令可用于取消定义一个本来已经是未定义的条件编译符号。下面的示例定义一个条件编译符号 A,然后两次取消定义该符号;第二个 #undef 没有作用但仍是有效的。

#define A

#undef A

#undef A


2.5.4 条件编译指令

条件编译指令用于按条件包含或排除源文件中的某些部分。

pp-conditional:pp 条件:)

pp-if-section   pp-elif-sectionsopt   pp-else-sectionopt   pp-endifpp if    pp elif 可选   pp else 可选   pp endif)

pp-if-section:pp if 节:)

whitespaceopt   #   whitespaceopt   if   whitespace   pp-expression   pp-new-line   conditional-sectionopt空白可选   #   空白可选   if   空白   pp 表达式   pp 新行   条件节可选

pp-elif-sections:pp elif 节:)

pp-elif-sectionpp elif
pp-elif-sections   pp-elif-sectionpp elif    pp elif

pp-elif-section:pp elif 节:)

whitespaceopt   #   whitespaceopt   elif   whitespace   pp-expression   pp-new-line   conditional-sectionopt空白可选   #   空白可选   elif   空白   pp 表达式   pp 新行   条件节可选

pp-else-section:pp-else 节:)

whitespaceopt   #   whitespaceopt   else   pp-new-line   conditional-sectionopt空白可选   #   空白可选   else   pp 新行   条件节可选

pp-endif:

whitespaceopt   #   whitespaceopt   endif   pp-new-line空白可选   #   空白可选   endif   pp 新行

conditional-section:(条件节:)

input-section输入节
skipped-section跳过节

skipped-section:(跳过节:)

skipped-section-part跳过节部分
skipped-section   skipped-section-part跳过节   跳过节部分

skipped-section-part:(跳过节部分:)

skipped-charactersopt   new-line跳过字符可选   新行
pp-directivepp 指令

skipped-characters:(跳过字符:)

whitespaceopt   not-number-sign   input-charactersopt空白可选   非数字符号   输入字符可选

not-number-sign:(非数字符号:)

# 外的任何输入字符

按照语法的规定条件编译指令必须写成集的形式集的组成依次为一个 #if 指令、一个或多个 #elif 指令或没有、一个或多个 #else 指令或没有和一个 #endif 指令。指令之间是源代码的条件节。每节代码直接位于它前面的那个指令控制。条件节本身可以包含嵌套的条件编译指令,前提是这些指令构成完整的指令集。

pp 条件最多只能选择一个它所包含的条件节去做通常的词法处理

·          按顺序计算 #if #elif 指令的pp 表达式直到获得值 true。如果表达式的结果为 true,则选择对应指令的“条件节”。

·          如果所有pp 表达式的结果都为 false 并且存在 #else 指令则选择 #else 指令的条件节

·          否则不选择任何“条件节”。

选定的条件节”(若有按正常的输入节处理节中包含的源代码必须符合词法文法从节中的源代码生成标记节中的预处理指令具有规定的效果。

剩余的条件节”(若有跳过节处理除了预处理指令节中的源代码不必一定要符合词法文法不从节中的源代码生成任何词法标记节中的预处理指令必须在词法上正确但不另外处理。在按“跳过节”处理的“条件节”中,任何嵌套的“条件节”(包含在嵌套的 #if...#endif#region...#endregion 构造中)也按“跳过节”处理。

下面的示例阐释如何嵌套条件编译指令

#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 表达式的计算。示例

#if X

     /*

#else

    /* */ class Q { }

#endif

总是产生同样的标记流 (class Q { }),不管是否定义了 X。如果定义了 X,由于多行注释的缘故,只处理 #if#endif 指令。如果未定义 X,则这三个指令(#if#else#endif)是指令集的组成部分。


2.5.5 诊断指令

诊断指令用于显式生成错误信息和警告消息这些信息的报告方式与其他编译时错误和警告相同。

pp-diagnostic:pp 诊断:)

whitespaceopt   #   whitespaceopt   error   pp-message空白可选   #   空白可选   error   pp 消息
whitespaceopt   #   whitespaceopt   warning   pp-message空白可选   #   空白可选   warning   pp 消息

pp-message:pp 消息:)

new-line新行
whitespace
   input-charactersopt   new-line空白   输入字符可选   新行

示例

#warning Code review needed before check-in

#if Debug && Retail

    #error A build can't be both debug and retail

#endif

class Test {...}

总是产生一个警告(“Code review needed before check-in”),如果同时定义条件符号 Debug Retail则产生一个编译时错误(“A build can't be both debug and retail”)。注意 pp-message(pp 消息)可以包含任意文本;具体说来,它可以包含格式不正确的标记,比如“can't”中的单引号就是这样。


2.5.6 区域指令

区域指令用于显式标记源代码的区域。

pp-region:pp 区域:)

pp-start-region   conditional-sectionopt   pp-end-regionpp 开始区域   条件节可选   pp 结束区域

pp-start-region:pp 开始区域:)

whitespaceopt   #   whitespaceopt   region   pp-message空白可选   #   空白可选   region   pp 消息

pp-end-region:pp 结束区域:)

whitespaceopt   #   whitespaceopt   endregion   pp-message空白可选   #   空白可选   endregion   pp 消息

区域不具有任何附加的语义含义区域旨在由程序员或自动工具用来标记源代码中的节。#region#endregion 指令中指定的消息同样不具有任何语义含义;它只是用于标识区域。匹配的 #region #endregion 指令可能具有不同的pp 消息

区域的词法处理

#region

...

#endregion

与以下形式的条件编译指令的词法处理完全对应

#if true

...

#endif


2.5.7 行指令

行指令可用于改变编译器在输出如警告和错误中报告的行号和源文件名称。

行指令最通用于从某些其他文本输入生成 C# 源代码的元编程工具。

pp-line:pp 行:)

whitespaceopt   #   whitespaceopt   line   whitespace   line-indicator   pp-new-line空白可选   #   空白可选   line   空白   行指示符   pp 新行

line-indicator:(行指示符:)

decimal-digits   whitespace   file-name(十进制数字   空白   文件名)

decimal-digits十进制数字

default

hidden

file-name:(文件名:)

"   file-name-characters   "("   文件名字符   ")

file-name-characters:(文件名字符:)

file-name-character文件名字符
file-name-characters   file-name-character文件名字符   文件名字符

file-name-character:(文件名字符:)

" 外的任何输入字符

当不存在 #line 指令时编译器在它的输出中报告真实的行号和源文件名称。#line 指令最通用于从某些其他文本输入生成 C# 源代码的元编程工具。当处理的 #line 指令包含不是 default 的行指示符时,编译器将该指令“后面”的行视为具有给定的行号(如果指定了,还包括文件名)。

#line default 指令消除前面所有 #line 指令的影响。编译器报告后续行的真实行信息,就像尚未处理任何 #line 指令一样。

#line hidden 指令对错误信息中报告的文件号和行号无效但对源代码级调试确实有效。调试时,#line hidden 指令和后面的 #line 指令(不是 #line hidden)之间的所有行都没有行号信息。在调试器中逐句执行代码时,将全部跳过这些行。

注意file-name文件名与常规字符串的不同之处在于不处理转义字符;“\字符在 file-name文件名中只是表示一个普通的反斜杆字符。

posted on 2006-04-26 15:06    阅读(443)  评论(0编辑  收藏  举报

导航