【C#语言规范版本5.0学习】2 词法结构(三、预处理指令)

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

pp-directive:
  pp-declaration
  pp-conditional
  pp-line
  pp-diagnostic
  pp-region
  pp-pragma

下面是可用的预处理指令:

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

 #if、#elif、#else 和 #endif,用于按条件跳过源代码中的节。

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

 #error 和 #warning,分别用于发出错误和警告。

 #region 和 #endregion,用于显式标记源代码中的节。

 #pragma,用于为编译器指定可选的上下文信息。

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

包含 #define、#undef、#if、#elif、#else、#endif、#line 或 #endregion 指令的源代码行可以用单行注释结束。

在包含预处理指令的源行上不允许使用带分隔符的注释(/* */ 样式的注释)。 预处理指令既不是标记,也不是 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() {}
}

因此,尽管上述两个程序在词法分析中完全不同,但它们在句法分析中是相同的。

条件编译符号 

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

conditional-symbol:

      Any identifier-or-keyword except true or false

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

当在预处理表达式中引用时,已定义的条件编译符号具有布尔值 true,未定义的条件编译符号具有布尔值 false。不要求在预处理表达式中引用条件编译符号之前显式声明它们。相反,未声明的符号只是 未定义的,因此具有值 false。 条件编译符号的命名空间与 C# 程序中的所有其他命名实体截然不同。只能在 #define 和 #undef 指令以及预处理表达式中引用条件编译符号。

⟰ 预处理表达式

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

pp-expression:
  whitespaceopt pp-or-expression whitespaceopt
pp-or-expression:
  pp-and-expression
  pp-or-expression whitespaceopt || whitespaceopt pp-and-expression
pp-and-expression:
  pp-equality-expression
  pp-and-expression whitespaceopt && whitespaceopt pp-equality-expression
pp-equality-expression:
  pp-unary-expression
  pp-equality-expression whitespaceopt == whitespaceopt pp-unary-expression
  pp-equality-expression whitespaceopt != whitespaceopt pp-unary-expression
pp-unary-expression:
  pp-primary-expression
 ! whitespaceopt pp-unary-expression
pp-primary-expression:
 true
 false
 conditional-symbol
 ( whitespaceopt pp-expression whitespaceopt )

当在预处理表达式中引用时,已定义的条件编译符号具有布尔值 true,未定义的条件编译符号具有布尔值 false。 预处理表达式的计算总是产生一个布尔值。预处理表达式的计算规则与常量表达式相同, 唯一的例外是:在这里,唯一可引用的用户定义实体是条件编译符号。

⟰ 声明指令

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

pp-declaration:
  whitespaceopt # whitespaceopt define whitespace conditional-symbol pp-new-line
  whitespaceopt # whitespaceopt undef whitespace conditional-symbol pp-new-line
pp-new-line:
  whitespaceopt single-line-commentopt new-line

对 #define 指令的处理使给定的条件编译符号成为已定义的符号(从跟在指令后面的源代码行开始)。 类似地,对 #undef 指令的处理使给定的条件编译符号成为未定义的符号(从跟在指令后面的源代码行 开始)源文件中的任何 #define 和 #undef 指令都必须出现在源文件中第一个 token的前面,否则将发生编译时错误。直观地讲,#define 和 #undef 指令必须位于源文件中所有“实代码”的前面。 示例:

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

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

#define A

#define A

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

#define A

#undef A

#undef A

⟰ 条件编译指令

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

pp-conditional:
  pp-if-section pp-elif-sectionsopt pp-else-sectionopt pp-endif
pp-if-section:
  whitespaceopt # whitespaceopt if whitespace pp-expression pp-new-line conditionalsectionopt
pp-elif-sections:
  pp-elif-section
  pp-elif-sections pp-elif-section
pp-elif-section:
whitespaceopt # whitespaceopt elif whitespace pp-expression pp-new-line conditionalsectionopt
pp-else-section:
whitespaceopt # whitespaceopt else pp-new-line conditional-sectionopt
pp-endif:
whitespaceopt # whitespaceopt endif pp-new-line
conditional-section:
input-section
skipped-section
skipped-section:
skipped-section-part
skipped-section skipped-section-part
skipped-section-part:
skipped-charactersopt new-line
pp-directive
skipped-characters:
whitespaceopt not-number-sign input-charactersopt
not-number-sign:
Any input-character except #

按照语法的规定,条件编译指令必须写成集的形式,集的组成依次为:一个 #if 指令、一个或多个 #elif 指令(或没有)、一个或多个 #else 指令(或没有)和一个 #endif 指令。

指令之间是源代码的条件节。每节代码直接位于它前面的那个指令控制。条件节本身可以包含嵌套的条件编译指令,前提是这些指令构成完整的指令集。

pp-conditional 最多只能选择它所包含的 conditional-section 之一去做通常的词法处理:

 按顺序计算 #if 和 #elif 指令的 pp-expression,直到得出 true 值。如果表达式的结果为 true,则选择对应指令的 conditional-section

 如果所有 pp-expression 的结果都为 false 并且存在 #else 指令,则选择 #else 指令的 conditionalsection

 否则不选择任何 conditional-section

所选的 conditional-section(如果有)作为正常 input-section 进行处理:节中包含的源代码必须符合词法文法;标记由节中的源代码生成;并且节中的预处理指令具有规定的效果。剩余的 conditional-section(如果有)作为 skipped-sections 进行处理:除了预处理指令,节中的源代码不必一定要符合词法文法;不从节中的源代码生成任何词法标记;节中的预处理指令必须在词法上正确, 但不另外处理。在按 skipped-section 处理的 conditional-section 中,任何嵌套的 conditional-section(包含在嵌套的 #if...#endif 和 #region...#endregion 构造中)也按 skipped-section 处理。 下面的示例阐释如何嵌套条件编译指令:

#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,则这三个指令(#if、#else、#endif)都是指令集的组成部分。

⟰ 诊断指令

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

pp-diagnostic:
  whitespaceopt # whitespaceopt error pp-message
  whitespaceopt # whitespaceopt warning pp-message
pp-message:
  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 可以包含任意文本;具体而言,它可以包含格式不正确的标记,比如单词 can’t 中的单引号。

⟰ 区域指令

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

pp-region:
  pp-start-region conditional-sectionopt pp-end-region
pp-start-region:
  whitespaceopt # whitespaceopt region pp-message
pp-end-region:
  whitespaceopt # whitespaceopt endregion pp-message

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

#region

...

#endregion

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

#if true

...

#endif

⟰ 行指令

行指令可用于变更编译器在输出(如警告和错误)中报告的行号和源文件名称以及调用方信息特性所使用的行号和源文件名称。 行指令最常用于从某些其他文本输入生成 C# 源代码的元编程工具。

pp-line:
  whitespaceopt # whitespaceopt line whitespace line-indicator pp-new-line
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: Any input-character except "

当不存在 #line 指令时,编译器在它的输出中报告真实的行号和源文件名称。当处理的 #line 指令包含不是 default 的 line-indicator 时,编译器将该指令后面的行视为具有给定的行号(如果指定了,还包括文件名)。 #line default 指令消除前面所有 #line 指令的影响。编译器报告后续行的真实行信息,就像尚未处理任何 #line 指令一样。 #line hidden 指令对错误信息中报告的文件号和行号无效,但对源代码级调试确实有效。调试时, #line hidden 指令和后面的 #line 指令(不是 #line hidden)之间的所有行都没有行号信息。在调试器中逐句执行代码时,将全部跳过这些行。 注意,file-name 与正则字符串的不同之处在于前者不处理转义字符;“”字符在 file-name 中只是表示一个普通的反斜杠字符。

⟰ Pragma 指令

#pragma 预处理指令用来向编译器指定可选的上下文信息。#pragma 指令中提供的信息永远不会更改程序语义。

pp-pragma:
  whitespaceopt # whitespaceopt pragma whitespace pragma-body pp-new-line
pragma-body:
  pragma-warning-body

C# 提供 #pragma 指令以控制编译器警告。此语言将来的版本可能包含更多的 #pragma 指令。为了确保与其他 C# 编译器的互操作性,Microsoft C# 编译器对于未知的 #pragma 指令不会发出编译错误;但是这类指令确实会生成警告。

Pragma warning

#pragma warning 指令用于在编译后续程序文本的过程中禁用或恢复所有或特定的一部分警告消息 tex。

pragma-warning-body:
  warning whitespace warning-action
  warning whitespace warning-action whitespace warning-list
warning-action:
  disable
  restore
warning-list:
  decimal-digits
  warning-list whitespaceopt , whitespaceopt decimal-digits

省略了警告列表的 #pragma warning 指令将影响所有警告。包含警告列表的 #pragma warning 指令只影响该列表中列出的警告。 #pragma warning disable 指令将禁用所有警告或给定的一组警告。 #pragma warning restore 指令将所有警告或指定警告恢复为在编译单元的开始处有效的状态。请注意,如果在外部禁用了特定的警告,则 #pragma warning restore(无论是恢复所有警告还是恢复特定警告)将不会重新启用该警告。 下面的示例演示的是使用 #pragma warning 临时禁用在通过 Microsoft C# 编译器中的警告编号引用已过时的成员时报告的警告。

using System;
class Program
{
[Obsolete]
static void Foo() {}
static void Main() {
#pragma warning disable 612
Foo();
#pragma warning restore 612
}
}
原文地址:https://www.cnblogs.com/TechSingularity/p/14346001.html