C# 预处理器指令C# preprocessor directives

尽管编译器没有单独的预处理器,但本节中所述指令的处理方式与有预处理器时一样。Although the compiler doesn't have a separate preprocessor, the directives described in this section are processed as if there were one. 可使用这些指令来帮助条件编译。You use them to help in conditional compilation. 不同于 C 和 C++ 指令,不能使用这些指令来创建宏。Unlike C and C++ directives, you can't use these directives to create macros. 预处理器指令必须是一行中唯一的说明。A preprocessor directive must be the only instruction on a line.

可为空上下文Nullable context

#nullable 预处理器指令将设置可为空注释上下文和可为空警告上下文 。The #nullable preprocessor directive sets the nullable annotation context and nullable warning context. 此指令控制是否可为空注释是否有效,以及是否给出为 Null 性警告。This directive controls whether nullable annotations have effect, and whether nullability warnings are given. 每个上下文要么处于已禁用状态,要么处于已启用状态 。Each context is either disabled or enabled.

可在项目级别(C# 源代码之外)指定这两个上下文。Both contexts can be specified at the project level (outside of C# source code). #nullable 指令控制注释和警告上下文,并优先于项目级设置。The #nullable directive controls the annotation and warning contexts and takes precedence over the project-level settings. 指令会设置其控制的上下文,直到另一个指令替代它,或直到源文件结束为止。A directive sets the context(s) it controls until another directive overrides it, or until the end of the source file.

指令的效果如下所示:The effect of the directives is as follows:

  • #nullable disable:将可为空注释和警告上下文设置为“已禁用”。#nullable disable: Sets the nullable annotation and warning contexts to disabled.
  • #nullable enable:将可为空注释和警告上下文设置为“已启用”。#nullable enable: Sets the nullable annotation and warning contexts to enabled.
  • #nullable restore:将可为空注释和警告上下文还原为项目设置。#nullable restore: Restores the nullable annotation and warning contexts to project settings.
  • #nullable disable annotations:将可为空注释上下文设置为“已禁用”。#nullable disable annotations: Sets the nullable annotation context to disabled.
  • #nullable enable annotations:将可为空注释上下文设置为“已启用”。#nullable enable annotations: Sets the nullable annotation context to enabled.
  • #nullable restore annotations:将可为空注释上下文还原为项目设置。#nullable restore annotations: Restores the nullable annotation context to project settings.
  • #nullable disable warnings:将可为空警告上下文设置为“已禁用”。#nullable disable warnings: Sets the nullable warning context to disabled.
  • #nullable enable warnings:将可为空警告上下文设置为“已启用”。#nullable enable warnings: Sets the nullable warning context to enabled.
  • #nullable restore warnings:将可为空警告上下文还原为项目设置。#nullable restore warnings: Restores the nullable warning context to project settings.

条件编译Conditional compilation

使用四个预处理器指令来控制条件编译:You use four preprocessor directives to control conditional compilation:

  • #if:打开条件编译,其中仅在定义了指定的符号时才会编译代码。#if: Opens a conditional compilation, where code is compiled only if the specified symbol is defined.
  • #elif:关闭前面的条件编译,并基于是否定义了指定的符号打开一个新的条件编译。#elif: Closes the preceding conditional compilation and opens a new conditional compilation based on if the specified symbol is defined.
  • #else:关闭前面的条件编译,如果没有定义前面指定的符号,打开一个新的条件编译。#else: Closes the preceding conditional compilation and opens a new conditional compilation if the previous specified symbol isn't defined.
  • #endif:关闭前面的条件编译。#endif: Closes the preceding conditional compilation.

如果 C# 编译器遇到 #if 指令,最后跟着一个 #endif 指令,则仅当定义指定的符号时,它才编译这些指令之间的代码。When the C# compiler finds an #if directive, followed eventually by an #endif directive, it compiles the code between the directives only if the specified symbol is defined. 与 C 和 C++ 不同,不能将数字值分配给符号。Unlike C and C++, you can't assign a numeric value to a symbol. C# 中的 #if 语句是布尔值,且仅测试是否已定义该符号。The #if statement in C# is Boolean and only tests whether the symbol has been defined or not. 例如:For example:

#if DEBUG
    Console.WriteLine("Debug version");
#endif

可以使用运算符 ==(相等)!=(不相等)来测试 bool 值是 true 还是 falseYou can use the operators == (equality) and != (inequality) to test for the bool values true or false. true 表示定义该符号。true means the symbol is defined. 语句 #if DEBUG 具有与 #if (DEBUG == true) 相同的含义。The statement #if DEBUG has the same meaning as #if (DEBUG == true). 可以使用 && (and)|| (or)!(not) 运算符来计算是否已定义多个符号。You can use the && (and), || (or), and ! (not) operators to evaluate whether multiple symbols have been defined. 还可以用括号对符号和运算符进行分组。You can also group symbols and operators with parentheses.

#if 以及 #else#elif#endif#define#undef 指令,允许基于是否存在一个或多个符号包括或排除代码。#if, along with the #else, #elif, #endif, #define, and #undef directives, lets you include or exclude code based on the existence of one or more symbols. 条件编译在编译调试版本的代码或编译特定配置的代码时会很有用。Conditional compilation can be useful when compiling code for a debug build or when compiling for a specific configuration.

#if 指令开头的条件指令必须以 #endif 指令显式终止。A conditional directive beginning with an #if directive must explicitly be terminated with an #endif directive. #define 允许你定义一个符号。#define lets you define a symbol. 通过将该符号用作传递给 #if 指令的表达式,该表达式的计算结果为 trueBy using the symbol as the expression passed to the #if directive, the expression evaluates to true. 还可以通过 DefineConstants 编译器选项来定义符号。You can also define a symbol with the DefineConstants compiler option. 可以通过 #undef 取消定义符号。You can undefine a symbol with #undef. 使用 #define 创建的符号的作用域是在其中定义它的文件。The scope of a symbol created with #define is the file in which it was defined. 使用 DefineConstants 或 #define 定义的符号与具有相同名称的变量不冲突。A symbol that you define with DefineConstants or with #define doesn't conflict with a variable of the same name. 也就是说,变量名称不应传递给预处理器指令,且符号仅能由预处理器指令评估。That is, a variable name shouldn't be passed to a preprocessor directive, and a symbol can only be evaluated by a preprocessor directive.

#elif 可以创建复合条件指令。#elif lets you create a compound conditional directive. 如果之前的 #if 和任何之前的可选 #elif 指令表达式的值都不为 true,则计算 #elif 表达式。The #elif expression will be evaluated if neither the preceding #if nor any preceding, optional, #elif directive expressions evaluate to true. 如果 #elif 表达式计算结果为 true,编译器将计算 #elif 和下一条件指令间的所有代码。If an #elif expression evaluates to true, the compiler evaluates all the code between the #elif and the next conditional directive. 例如:For example:

#define VC7
//...
#if debug
    Console.WriteLine("Debug build");
#elif VC7
    Console.WriteLine("Visual Studio 7");
#endif

#else 允许创建复合条件指令,因此,如果先前 #if 或(可选)#elif 指令中的任何表达式的计算结果都不是 true,则编译器将对介于 #else 和下一个 #endif 之间的所有代码进行求值。#else lets you create a compound conditional directive, so that, if none of the expressions in the preceding #if or (optional) #elif directives evaluate to true, the compiler will evaluate all code between #else and the next #endif. #endif(#endif) 必须是 #else 之后的下一个预处理器指令。#endif(#endif) must be the next preprocessor directive after #else.

#endif 指定条件指令的末尾,以 #if 指令开头。#endif specifies the end of a conditional directive, which began with the #if directive.

此外,生成系统还会感知表示 SDK 样式项目中不同目标框架的预定义预处理器符号。The build system is also aware of predefined preprocessor symbols representing different target frameworks in SDK-style projects. 在创建可以面向多个 .NET 版本的应用程序时,这些符号会很有用。They're useful when creating applications that can target more than one .NET version.

目标框架Target Frameworks 符号Symbols
.NET Framework.NET Framework NETFRAMEWORK, NET48, NET472, NET471, NET47, NET462, NET461, NET46, NET452, NET451, NET45, NET40, NET35, NET20NETFRAMEWORK, NET48, NET472, NET471, NET47, NET462, NET461, NET46, NET452, NET451, NET45, NET40, NET35, NET20
.NET Standard.NET Standard NETSTANDARD, NETSTANDARD2_1, NETSTANDARD2_0, NETSTANDARD1_6, NETSTANDARD1_5, NETSTANDARD1_4, NETSTANDARD1_3, NETSTANDARD1_2, NETSTANDARD1_1, NETSTANDARD1_0NETSTANDARD, NETSTANDARD2_1, NETSTANDARD2_0, NETSTANDARD1_6, NETSTANDARD1_5, NETSTANDARD1_4, NETSTANDARD1_3, NETSTANDARD1_2, NETSTANDARD1_1, NETSTANDARD1_0
.NET 5(和 .NET Core).NET 5 (and .NET Core) NET, NET5_0, NETCOREAPP, NETCOREAPP3_1, NETCOREAPP3_0, NETCOREAPP2_2, NETCOREAPP2_1, NETCOREAPP2_0, NETCOREAPP1_1, NETCOREAPP1_0NET, NET5_0, NETCOREAPP, NETCOREAPP3_1, NETCOREAPP3_0, NETCOREAPP2_2, NETCOREAPP2_1, NETCOREAPP2_0, NETCOREAPP1_1, NETCOREAPP1_0

备注

对于传统的非 SDK 样式的项目,必须通过项目的属性页面在 Visual Studio 中为不同目标框架手动配置条件编译符号。For traditional, non-SDK-style projects, you have to manually configure the conditional compilation symbols for the different target frameworks in Visual Studio via the project's properties pages.

其他预定义符号包括 DEBUGTRACE 常数。Other predefined symbols include the DEBUG and TRACE constants. 你可以使用 #define 替代项目的值集。You can override the values set for the project using #define. 例如,会根据生成配置属性(“调试”或者“发布”模式)自动设置 DEBUG 符号。The DEBUG symbol, for example, is automatically set depending on your build configuration properties ("Debug" or "Release" mode).

下例显示如何在文件上定义 MYTEST 符号,然后测试 MYTESTDEBUG 符号的值。The following example shows you how to define a MYTEST symbol on a file and then test the values of the MYTEST and DEBUG symbols. 此示例的输出取决于是在“调试”还是“发布”配置模式下生成项目 。The output of this example depends on whether you built the project on Debug or Release configuration mode.

#define MYTEST
using System;
public class MyClass
{
    static void Main()
    {
#if (DEBUG && !MYTEST)
        Console.WriteLine("DEBUG is defined");
#elif (!DEBUG && MYTEST)
        Console.WriteLine("MYTEST is defined");
#elif (DEBUG && MYTEST)
        Console.WriteLine("DEBUG and MYTEST are defined");  
#else
        Console.WriteLine("DEBUG and MYTEST are not defined");
#endif
    }
}

下例显示如何针对不同的目标框架进行测试,以便在可能时使用较新的 API:The following example shows you how to test for different target frameworks so you can use newer APIs when possible:

public class MyClass
{
    static void Main()
    {
#if NET40
        WebClient _client = new WebClient();
#else
        HttpClient _client = new HttpClient();
#endif
    }
    //...
}

定义符号Defining symbols

使用以下两个预处理器指令来定义或取消定义条件编译的符号:You use the following two preprocessor directives to define or undefine symbols for conditional compilation:

  • #define:定义符号。#define: Define a symbol.
  • #undef:取消定义符号。#undef: undefine a symbol.

使用 #define 来定义符号。You use #define to define a symbol. 将符号用作传递给 #if 指令的表达式时,该表达式的计算结果为 true,如以下示例所示:When you use the symbol as the expression that's passed to the #if directive, the expression will evaluate to true, as the following example shows:

#define VERBOSE

#if VERBOSE
   Console.WriteLine("Verbose output version");
#endif

备注

#define 指令不能用于声明常量值,这与 C 和 C++ 中的通常做法一样。The #define directive cannot be used to declare constant values as is typically done in C and C++. C# 中的常量最好定义为类或结构的静态成员。Constants in C# are best defined as static members of a class or struct. 如果具有多个此类常量,请考虑创建一个单独的“常量”类来容纳它们。If you have several such constants, consider creating a separate "Constants" class to hold them.

符号可用于指定编译的条件。Symbols can be used to specify conditions for compilation. 可通过 #if#elif 测试符号。You can test for the symbol with either #if or #elif. 还可以使用 ConditionalAttribute 来执行条件编译。You can also use the ConditionalAttribute to perform conditional compilation. 可以定义符号,但不能为符号分配值。You can define a symbol, but you can't assign a value to a symbol. 文件中必须先出现 #define 指令,才能使用并非同时也是预处理器指令的任何指示。The #define directive must appear in the file before you use any instructions that aren't also preprocessor directives. 还可以通过 DefineConstants 编译器选项来定义符号。You can also define a symbol with the DefineConstants compiler option. 可以通过 #undef 取消定义符号。You can undefine a symbol with #undef.

定义区域Defining regions

可以使用以下两个预处理器指令来定义可在大纲中折叠的代码区域:You can define regions of code that can be collapsed in an outline using the following two preprocessor directives:

  • #region:启动区域。#region: Start a region.
  • #endregion:结束区域#endregion: End a region

利用 #region,可以指定在使用代码编辑器的大纲功能时可展开或折叠的代码块。#region lets you specify a block of code that you can expand or collapse when using the outlining feature of the code editor. 在较长的代码文件中,折叠或隐藏一个或多个区域十分便利,这样,可将精力集中于当前处理的文件部分。In longer code files, it's convenient to collapse or hide one or more regions so that you can focus on the part of the file that you're currently working on. 下面的示例演示如何定义区域:The following example shows how to define a region:

#region MyClass definition
public class MyClass
{
    static void Main()
    {
    }
}
#endregion

#region 块必须通过 #endregion 指令终止。A #region block must be terminated with an #endregion directive. #region 块不能与 #if 块重叠。A #region block can't overlap with an #if block. 但是,可以将 #region 块嵌套在 #if 块内,或将 #if 块嵌套在 #region 块内。However, a #region block can be nested in an #if block, and an #if block can be nested in a #region block.

错误和警告信息Error and warning information

使用以下指令指示编译器生成用户定义的编译器错误和警告,并控制行信息:You instruct the compiler to generate user-defined compiler errors and warnings, and control line information using the following directives:

  • #error:使用指定的消息生成编译器错误。#error: Generate a compiler error with a specified message.
  • #warning:使用指定的消息生成编译器警告。#warning: Generate a compiler warning, with a specific message.
  • #line:更改用编译器消息输出的行号。#line: Change the line number printed with compiler messages.

#error 可从代码中的特定位置生成 CS1029 用户定义的错误。#error lets you generate a CS1029 user-defined error from a specific location in your code. 例如:For example:

#error Deprecated code in this method.

备注

编译器以特殊的方式处理 #error version 并报告编译器错误 CS8304,消息中包含使用的编译器和语言版本。The compiler treats #error version in a special way and reports a compiler error, CS8304, with a message containing the used compiler and language versions.

#warning 允许你从代码中的特定位置生成 CS1030 第一级编译器警告。#warning lets you generate a CS1030 level one compiler warning from a specific location in your code. 例如:For example:

#warning Deprecated code in this method.

借助 #line,可修改编译器的行号及(可选)用于错误和警告的文件名输出。#line lets you modify the compiler's line numbering and (optionally) the file name output for errors and warnings.

以下示例演示如何报告与行号相关联的两个警告。The following example shows how to report two warnings associated with line numbers. #line 200 指令将下一行的行号强制设为 200(尽管默认值为 #6);在执行下一个 #line 指令前,文件名都会报告为“特殊”。The #line 200 directive forces the next line's number to be 200 (although the default is #6), and until the next #line directive, the filename will be reported as "Special". #line default 指令将行号恢复至默认行号,这会对上一指令重新编号的行进行计数。The #line default directive returns the line numbering to its default numbering, which counts the lines that were renumbered by the previous directive.

class MainClass
{
    static void Main()
    {
#line 200 "Special"
        int i;
        int j;
#line default
        char c;
        float f;
#line hidden // numbering not affected
        string s;
        double d;
    }
}

编译产生以下输出:Compilation produces the following output:

Special(200,13): warning CS0168: The variable 'i' is declared but never used
Special(201,13): warning CS0168: The variable 'j' is declared but never used
MainClass.cs(9,14): warning CS0168: The variable 'c' is declared but never used
MainClass.cs(10,15): warning CS0168: The variable 'f' is declared but never used
MainClass.cs(12,16): warning CS0168: The variable 's' is declared but never used
MainClass.cs(13,16): warning CS0168: The variable 'd' is declared but never used

可在生成过程的自动、中间步骤中使用 #line 指令。The #line directive might be used in an automated, intermediate step in the build process. 例如,如果已从原始源代码文件中删除行,但仍希望编译器基于文件中的原始行号生成输出,可在删除行后,使用 #line 来模拟原始行号。For example, if lines were removed from the original source code file, but you still wanted the compiler to generate output based on the original line numbering in the file, you could remove lines and then simulate the original line numbering with #line.

#line hidden 指令能对调试程序隐藏连续行,当开发者逐行执行代码时,介于 #line hidden 和下一 #line 指令(假设它不是其他 #line hidden 指令)间的任何行都将被跳过。The #line hidden directive hides the successive lines from the debugger, such that when the developer steps through the code, any lines between a #line hidden and the next #line directive (assuming that it isn't another #line hidden directive) will be stepped over. 还可通过此选项允许 ASP.NET 区分用户定义和计算机生成的代码。This option can also be used to allow ASP.NET to differentiate between user-defined and machine-generated code. 虽然此功能主要用于 ASP.NET,但可能更多的源生成器会利用此功能。Although ASP.NET is the primary consumer of this feature, it's likely that more source generators will make use of it.

#line hidden 指令不影响错误报告中的文件名或行号。A #line hidden directive doesn't affect file names or line numbers in error reporting. 也就是说,如果编译器在隐藏块中发现错误,编译器将报告错误的当前文件名和行号。That is, if the compiler finds an error in a hidden block, the compiler will report the current file name and line number of the error.

#line filename 指令可指定要在编译器输出中显示的文件名。The #line filename directive specifies the file name you want to appear in the compiler output. 默认情况下,将使用源代码文件的实际名称。By default, the actual name of the source code file is used. 该文件名必须以双引号 ("") 引起来,且必须位于行号之后。The file name must be in double quotation marks ("") and must be preceded by a line number.

下列示例演示调试程序如何忽略代码中的隐藏行。The following example shows how the debugger ignores the hidden lines in the code. 运行示例时,它将显示三行文本。When you run the example, it will display three lines of text. 但是,如果按照示例所示设置断点、并按 F10 逐行执行代码,调试程序将忽略隐藏行。However, when you set a break point, as shown in the example, and hit F10 to step through the code, the debugger ignores the hidden line. 即使在隐藏行设置断点,调试程序仍将忽略它。Even if you set a break point at the hidden line, the debugger will still ignore it.

// preprocessor_linehidden.cs
using System;
class MainClass
{
    static void Main()
    {
        Console.WriteLine("Normal line #1."); // Set break point here.
#line hidden
        Console.WriteLine("Hidden line.");
#line default
        Console.WriteLine("Normal line #2.");
    }
}

杂注Pragmas

#pragma 为编译器给出特殊指令以编译它所在的文件。#pragma gives the compiler special instructions for the compilation of the file in which it appears. 这些指令必须受编译器支持。The instructions must be supported by the compiler. 换句话说,不能使用 #pragma 创建自定义的预处理指令。In other words, you can't use #pragma to create custom preprocessing instructions.

#pragma pragma-name pragma-arguments

其中 pragma-name 是可识别 pragma 的名称,pragma-arguments 是特定于 pragma 的参数。Where pragma-name is the name of a recognized pragma and pragma-arguments is the pragma-specific arguments.

#pragma warning#pragma warning

#pragma warning 可以启用或禁用特定警告。#pragma warning can enable or disable certain warnings.

#pragma warning disable warning-list
#pragma warning restore warning-list

其中 warning-list 是以逗号分隔的警告编号的列表。Where warning-list is a comma-separated list of warning numbers. “CS”前缀是可选的。The "CS" prefix is optional. 未指定警告编号时,disable 会禁用所有警告,restore 会启用所有警告。When no warning numbers are specified, disable disables all warnings and restore enables all warnings.

备注

若要在 Visual Studio 中查找警告编号,请生成项目,然后在“输出”窗口中查找警告编号。To find warning numbers in Visual Studio, build your project and then look for the warning numbers in the Output window.

disable 从源文件的下一行开始生效。The disable takes effect beginning on the next line of the source file. 警告会在后面的 restore 行上还原。The warning is restored on the line following the restore. 如果文件中没有 restore,则在同一编译中任何之后文件的第一行,警告将还原为其默认状态。If there's no restore in the file, the warnings are restored to their default state at the first line of any later files in the same compilation.

// pragma_warning.cs
using System;

#pragma warning disable 414, CS3021
[CLSCompliant(false)]
public class C
{
    int i = 1;
    static void Main()
    {
    }
}
#pragma warning restore CS3021
[CLSCompliant(false)]  // CS3021
public class D
{
    int i = 1;
    public static void F()
    {
    }
}

#pragma checksum#pragma checksum

生成源文件的校验和以帮助调试 ASP.NET 页面。Generates checksums for source files to aid with debugging ASP.NET pages.

#pragma checksum "filename" "{guid}" "checksum bytes"

其中,"filename" 是需要监视更改或更新的文件的名称,"{guid}" 是哈希算法的全局唯一标识符 (GUID),"checksum_bytes" 是表示校验和字节的十六进制数字的字符串。Where "filename" is the name of the file that requires monitoring for changes or updates, "{guid}" is the Globally Unique Identifier (GUID) for the hash algorithm, and "checksum_bytes" is the string of hexadecimal digits representing the bytes of the checksum. 必须是偶数个十六进制数字。Must be an even number of hexadecimal digits. 奇数个数字会导致编译时警告出现,且指令遭忽略。An odd number of digits results in a compile-time warning, and the directive is ignored.

Visual Studio 调试器使用校验和确保它可始终找到正确的源。The Visual Studio debugger uses a checksum to make sure that it always finds the right source. 编译器为源文件计算校验和,然后将输出发出到程序数据库 (PDB) 文件。The compiler computes the checksum for a source file, and then emits the output to the program database (PDB) file. 调试器随后使用 PDB 针对它为源文件计算的校验和进行比较。The debugger then uses the PDB to compare against the checksum that it computes for the source file.

此解决方案不适用于 ASP.NET 项目,因为计算出的校验和是针对生成的源文件,而不是 .aspx 文件。This solution doesn't work for ASP.NET projects, because the computed checksum is for the generated source file, rather than the .aspx file. 为解决此问题,#pragma checksum 为 ASP.NET 页面提供校验和支持。To address this problem, #pragma checksum provides checksum support for ASP.NET pages.

在 Visual C# 中创建 ASP.NET 项目时,生成的源文件包含 .aspx 文件(从该文件生成源)的校验和。When you create an ASP.NET project in Visual C#, the generated source file contains a checksum for the .aspx file, from which the source is generated. 编译器随后将此信息写入 PDB 文件中。The compiler then writes this information into the PDB file.

如果编译器在文件中没有找到 #pragma checksum 指令,它将计算校验和,并将该值写入 PDB 文件。If the compiler doesn't find a #pragma checksum directive in the file, it computes the checksum and writes the value to the PDB file.

class TestClass
{
    static int Main()
    {
        #pragma checksum "file.cs" "{406EA660-64CF-4C82-B6F0-42D48172A799}" "ab007f1d23d9" // New checksum
    }
}