在C/C++中,实际上有两个编译回合,通过预处理器指令可以改写源代码,使它在第二遍编译前与特定的结构和应用相对应。C#的编译过程不是两个阶段,不过,C#对待预处理器指令的方式与C/C++相似。
C# 语言的预处理器指令:
- #if
- #else
- #elif
- #endif
- # define
- #undef
- #warning
- #error
- #line
- #region
- #endregion
- #pragma
- #pragma warning
- #pragma checksum
一、#if
#if 使您可以开始条件指令,测试一个或多个符号以查看它们是否计算为 true。如果它们的计算结果确实为 true,则编译器将计算位于 #if 与最近的 #endif 指令之间的所有代码。以 #if 指令开始的条件指令必须用 #endif 指令显式终止。例如:
#define DEBUG
// ...
#if DEBUG
Console.WriteLine("Debug version");
#endif
可以使用运算符 ==(相等)、!=(不相等)、&&(与)及 ||(或)来计算多个符号。还可以用括号将符号和运算符分组。
备注:
使用 #if 以及 #else、#elif、#endif、#define 和 #undef 指令,可以包括或排除基于由一个或多个符号组成的条件的代码。这在编译调试版本的代码或编译特定配置时最为有用。
二、#else
#else 允许您创建复合条件指令,因此,如果前面的 #if 或(可选)#elif 指令中的任何表达式都不为 true,则编译器将计算 #else 与后面的 #endif 之间的所有代码。
备注:#endif必须是#else的下一个预处理器指令。
三、#elif
使您得以创建复合条件指令。如果前面的 #if 和前面的任何 #elif(可选)指令表达式的计算结果都不是 true,则将计算 #elif 表达式。如果 #elif 表达式计算为 true,编译器将计算位于 #elif 和下一个条件指令之间的所有代码。
三、#endif
#endif 指定以 #if 指令开头的条件指令的结尾。
四、#define
使用 #define 可以定义一个符号,并通过将该符号用作表达式传递给 #if 指令,使该表达式的计算结果为 true。可以定义符号,但是无法对符号赋值。例如:
# define DEBUG
符号可用于指定编译的条件。可以使用 #if 或 #elif 来测试符号。还可以使用 conditional 属性执行条件编译。
也可以用 /define 编译器选项来定义符号。可以用 #undef 来取消定义符号。 用 #define 创建的符号的范围是在其中定义该符号的文件。
备注:与C/C++不一样,C#的#define语句仅允许你在符号表中插一个标签,但你不能给任何一个符号赋一个值。与C/C++另一个不同的地方,C/C++允许#define预处理器指令出现在任何所需的地方,而在使用任何非预处理器指令的指令前,C#的#define指令必须出现在一个文件中。用#define定义的标签不会与同名的变量冲突。这就是说,一个变量名不应该传递给预处理器指令,而且符号只能通过预处理器指令来求值。插入到符号表中的标签的作用域就是定义它的文件。可以用#undef取消符号定义。
五、#undef
#undef 使您可以取消符号的定义,以便通过将该符号用作 #if 指令中的表达式,使表达式的计算结果为 false。
六、#warning
#warning 使您得以从代码的特定位置生成一级警告。例如:
#warning Deprecated code in this method.
备注:
#warning 通常用在条件指令中。也可以用 #error(C# 参考)生成用户定义的错误。
示例
// preprocessor_warning.cs
// CS1030 expected
#define DEBUG
class MainClass
{
static void Main()
{
#if DEBUG
#warning DEBUG is defined
#endif
}
}
七、#error
#error 使您可以从代码中的特定位置生成错误。例如:
#error Deprecated code in this method.
八、#line
#line 使您可以修改编译器的行号以及(可选)错误和警告的文件名输出。下面的示例说明如何报告与行号关联的两个警告。#line 200 指令强迫行号为 200(尽管默认值为 #7)。另一行 (#9) 作为默认 #line 指令的结果跟在通常序列后。
class MainClass
{
static void Main()
{
#line 200
int i; // CS0168 on line 200
#line default
char c; // CS0168 on line 9
}
}
备注:
#line 指令可能由生成过程中的自动中间步骤使用。例如,如果行从原始的源代码文件中移除,但是您仍希望编译器基于文件中的原始行号生成输出,则可以移除行,然后用 #line 模拟原始行号。 #line hidden 指令对调试器隐藏若干连续的行,这样当开发人员在逐句通过代码时,将会跳过 #line hidden 和下一个 #line 指令(假定它不是另一个 #line hidden 指令)之间的所有行。此选项也可用来使 ASP.NET 能够区分用户定义的代码和计算机生成的代码。尽管 ASP.NET 是此功能的主要使用者,但很可能将有更多的源生成器使用它。
#line hidden 指令不会影响错误报告中的文件名或行号。即,如果在隐藏块中遇到错误,编译器将报告当前文件名和错误的行号。
#line filename 指令指定您希望出现在编译器输出中的文件名。默认情况下,使用源代码文件的实际名称。文件名必须括在双引号 ("") 中。 源代码文件可以具有 #line 指令的任何编号。
示例
下面的示例说明调试器如何忽略代码中的隐藏行。运行此示例时,它将显示三行文本。但是,当设置如示例所示的断点并按 F10 键逐句通过代码时,您将看到调试器忽略了隐藏行。还请注意,即使在隐藏行上设置断点,调试器仍会忽略它。
// preprocessor_linehidden.cs
using System;
class MainClass
{
static void Main()
{
Console.WriteLine("Normal line #1."); //这里设置断点
#line hidden
Console.WriteLine("Hidden line.");
#line default
Console.WriteLine("Normal line #2.");
}
}
九、#region
#region 使您可以在使用 Visual Studio 代码编辑器的大纲显示功能时指定可展开或折叠的代码块。例如:
#region MyClass definition
public class MyClass
{
static void Main()
{
}
}
#endregion
备注:
#region 块必须以 #endregion 指令终止。
#region 块不能与 #if 块重叠。但是,可以将 #region 块嵌套在 #if 块内,或将 #if 块嵌套在 #region 块内。
十、#endregion
#endregion 标记 #region 块的结尾。
十一、#pragma
#pragma 用于给编辑器提供特殊的指令,说明如何编译包含杂注的文件。
#pragma pragma-name pragma-arguments
参数
pragma-name
可识别杂注的名称。
pragma-arguments
杂注特定的参数。
十二、#pragma warning
#pragma warning 可用于启用或禁用某些警告。
#pragma warning disable warning-list
#pragma warning restore warning-list
参数
warning-list
警告编号的逗号分隔列表。只输入数字,不包括前缀 "CS"。
当没有指定警告编号时,disable 禁用所有警告,而 restore 启用所有警告。
示例
// pragma_warning.cs
using System;
#pragma warning disable 414, 3021
[CLSCompliant(false)]
public class C
{
int i = 1;
static void Main()
{
}
}
#pragma warning restore 3021
[CLSCompliant(false)] // CS3021
public class D
{
int i = 1;
public static void F()
{
}
}
十三、#pragma checksum
可用于生成源文件的校验和,以帮助调试 ASP.NET 页。
#pragma checksum "filename" "{guid}" "checksum bytes"
参数
"filename"
要求监视更改或更新的文件的名称。
"{guid}"
文件的全局唯一标识符 (GUID)。
"checksum_bytes"
十六进制数的字符串,表示校验和的字节。必须是偶数位的十六进制数。奇数位的数字会导致编译时警告,从而使指令被忽略。
备注:
Visual Studio 调试器使用校验和来确保找到的总是正确的源。编译器计算源文件的校验和,然后将输出发出到程序数据库 (PDB) 文件。最后,调试器使用 PDB 来比较它为源文件计算的校验和。
此解决方案不适用于 ASP.NET 项目,因为算出的是生成的源文件而不是 .aspx 文件的校验和。为解决此问题,#pragma checksum 为 ASP.NET 页提供了校验和支持。
在 Visual C# 中创建 ASP.NET 项目时,生成的源文件包含 .aspx 文件(从该文件生成源文件)的校验和。然后,编译器将此信息写入 PDB 文件。
如果编译器在该文件中没有遇到 #pragma checksum 指令,它将计算校验和,然后将算出的值写入 PDB 文件。
示例
class TestClass
{
static int Main()
{
#pragma checksum "file.cs" "{3673e4ca-6098-4ec1-890f-8fceb2a794a2}" "{012345678AB}" // New checksum
}
}