跳转至

C# 预处理器指令

预处理器指令指导编译器在实际编译开始之前对信息进行预处理。

所有的预处理器指令都是以 # 开始。且在一行上,只有空白字符可以出现在预处理器指令之前。预处理器指令不是语句,所以它们不以分号(;)结束。

C# 编译器没有一个单独的预处理器,但是,指令被处理时就像是有一个单独的预处理器一样。在 C# 中,预处理器指令用于在条件编译中起作用。与 C 和 C++ 不同的是,它们不是用来创建宏。一个预处理器指令必须是该行上的唯一指令。

1.预处理器指令列表

下表列出了 C# 中可用的预处理器指令:

预处理器指令 描述
#define 它用于定义一系列成为符号的字符。
#undef 它用于取消定义符号。
#if 它用于测试符号是否为真。
#else 它用于创建复合条件指令,与 #if 一起使用。
#elif 它用于创建复合条件指令。
#endif 指定一个条件指令的结束。
#line 它可以让您修改编译器的行数以及(可选地)输出错误和警告的文件名。
#error 它允许从代码的指定位置生成一个错误。
#warning 它允许从代码的指定位置生成一级警告。
#region 它可以让您在使用 Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块。
#endregion 它标识着 #region 块的结束。

2.常见指令

2.1 #define和#undef

  • #define的用法如下所示: #define DEBUG
  • 它告诉编译器存在给定名称的符号,在本例中是DEBUG. 这有点类似于声明一个变量,但这个变量没有真正的值,只是存在而已.
  • 这个符号不是实际代码的一部分,而只是在编译代码的时候存在. 在C#中他没有任何意义.
  • #undef 正好相反 -- 它删除符号的定义: #undef DEBUUG
  • 如果符号不存在, #undef就没有任何作用. 同样, 如果符号已经存在, 则#undef也不起作用. 必须把#define#undef命令放在C#源文件的开头未知, 在声明要编译医德任何对象的代码之前.
  • #define本身并没有什么用, 但与其他预处理器指令(特别是#if)结合使用时, 它的功能就非常强大了.
  • 这里应该注意一般C#语法的一些变化, 预处理器指令不用分号, 一般一行只有一条命令, 这是因为预处理器指令, C#不再要求命令使用分号分割. 如果它遇到一条预处理指令, 就回家定下一条命令在下一行上.

2.2#if #elif #else#endif

这些指令告诉编译器是否要编译某块代码, 示例如下:

int DosomeWork(double x)
{
    // do something
    #if DEBUG
    Console.WriteLine("x is" + x);
    #endif
}
  • 这段代码会像往常那样编译, 但Console.WriteLine 命令包含在#if语句中.这行代码只有在前面的#define命令定义了DEBUG后才会执行.
  • 当编译遇到#if语句后, 将先检查相关符号是否存在, 如果符号存在, 就编译#if子句中的代码, 否则, 编译器会忽略所有的代码, 直到遇到匹配的#endif指令为止.
  • 一般是在调试时定义符号DEBUG, 把与调试相关的代码放在#if子句中. 在完成了调试后, 就把#define语句注释掉, 所有的调试代码就会奇迹般的消失, 可执行文件也会变小, 最终用户不会被这些调试信息弄糊涂(显然, 要做很多测试, 确保代码在没有定义DEBUG的情况下也能工作).
  • 这项技术在CC++编程中十分常见, 称为条件编译(conditional compliation).
  • #elif(=else if)#else指令可以在#if块中, 其含义非常直观, 也可以嵌套#if块:

2.3#warning#error

  • 当编译器遇到#warning#error指令时, 会分别产生警告和错误.
  • 编译器遇到#warning, 会给用户显示#warning指令后面的文本, 之后编译继续进行.
  • 编译遇到#error, 就会给用户显示后面的文本, 作为一个编译错误信息, 然后会立即退出编译, 不会产生IL代码.
  • 使用#error指令可以检查#define语句是不是做错了什么事, 使用#warning语句可以让程序员想起做过什么事情

示例:

#if DEBUG && RELEASE
#error "你同时定义了调试和发布两种状态!"
#endif
#warning "不要忘记在 boss 测试代码的时候删除这一行!"
    Console.WriteLine("我讨厌这份工作")

2.4#region#endregion

  • #region#endregion 指令用于 把一段代码标记为一个块,如下所示:

c# #region Menber Field Declarations int x; double d; Currency balance; #endregion

  • 这看起来似乎没有什么用, 他不影响编译过程. 这些指令的优点是他们可以被某些编译器识别, 包括Visual Studio .NET 编译器. 这些编译器可以使用这些指令时代码在屏幕上更好的布局.

2.5#line

  • 这个指令可以改变编译器在警告和错误信息中显示的文件名和行号信息, 用#line default把行号恢复成默认的行号.

  • #line [ number [“file_name”] | default ]

  • number

    要为源代码文件中后面的行指定的编号.

  • "file_name"(可选)

    希望出现在编译器输出中的文件名. 默认情况下, 使用源代码文件的实际名称. 文件名必须写在双引号中.

  • default

    重置文件中的行编号

  • 备注

    #line可能是有生成过程中的自动中间步骤使用. 例如如果中间步骤从原始的源代码文件中移除行, 但是您仍希望编译器基于文件中的原始行号生成输出, 则可以移除行, 然后用#line模拟原始行号.

  • 下面的示例说明如何报告与行号关联的两个警告. #line 200 指令迫使行号为200(尽管默认值为#7). 另一行(#9)作为默认#line指令的结果跟在通常序列后

  • 示例1:

    c# // preprocessor_line.cs public class MyClass { public static void Main() { #line 200 int i; //line 200 #line default char c; //line 9 } }

  • #line 指令可以用于改变编译器在警告和错误信息中显示的文件名和行号信息,不常用。

    如果编写代码时,在把代码发送给编译器前,要使用某些软件包改变输入的代码,就可以使用这个指令,因为这意味着编译器报告的行号或文件名与文件中的行号或编辑的文件名不匹配。#line指令可以用于还原这种匹配。也可以使用语法#line default把行号还原为默认的行号:

  • c# #line 164 "Core.cs" // 在文件的第 164 行 // Core.cs, before the intermediate // package mangles it. // later on #line default // 恢复默认行号

2.6#pragma

  • 可以抑制或恢复指定的编译警告. 与命令行选项不同, #pragma指令可以在类和方法上执行, 对抑制什么警告和抑制的时间进行更精细的控制

c# #pragma warning disable 169 // 取消编号 169 的警告 (字段未使用警告) public class MyClass { int nfiled; // 编译整个 MyClass 类是不会发出警告 } #pragma warning restore 169 // 恢复编号 169 的警告