C# 8.0本质论
上QQ阅读APP看书,第一时间看更新

4.10 C#预处理器指令

控制流程语句在运行时求值条件表达式。相反,C#预处理器在编译时调用。预处理器指令告诉C#编译器要编译哪些代码,并指出如何处理代码中的特定错误和警告。C#预处理器指令还可告诉C#编译器有关代码组织的信息。

语言对比:C++——预处理

C和C++等语言用预处理器对代码进行整理,根据特殊的记号来执行特殊的操作。预处理器指令通常告诉编译器如何编译文件中的代码,而并不参与实际的编译过程。相反,C#编译器将预处理器指令作为对源代码执行的常规词法分析的一部分。其结果就是,C#不支持更高级的预处理器宏,它最多只允许定义常量。事实上,“预处理器”在C++中显得很贴切,但在C#中就属于用词不当。

每个预处理器指令都以#开头,而且必须一行写完。换行符(而不是分号)标志着预处理器指令的结束。

表4.5总结了所有预处理器指令。

表4.5 预处理器指令

由于本书中的示例代码都不完整,因此如果直接编译,会经常产生编译警告。对于示例代码来说,这些警告可以忽略。为了关闭特定的编辑警告,可以在源代码文件中添加#pragma disable-warning指令。表4.6列举了几个在本书代码中被关闭的警告。

表4.6 几个在本书代码中被关闭的警告

在本书示例代码中,会经常看到上面几个警告代号被#pragma warning disable指令关闭。

4.10.1 排除和包含代码

我们经常用预处理器指令控制何时以及如何包含代码。例如,要使代码兼容C# 2.0(及以后版本)和1.0的编译器,可指示在遇到1.0编译器时排除C# 2.0特有的代码。井字棋程序示例和代码清单4.55对此进行了演示。

代码清单4.55 遇到C# 1.x编译器就排除C# 2.0代码

本例调用了System.Console.Clear()方法,只有2.0或更高版本才支持。使用#if和#endif预处理器指令,这行代码只有在定义了预处理器符号CSHARP2PLUS的前提下才会编译。

预处理器指令的另一个应用是处理不同平台之间的差异,比如用WINDOWS和LINUX #if指令将Windows和Linux特有的API包围起来。开发者经常用这些指令取代多行注释(/*...*/),因为它们更容易通过定义恰当的符号或通过搜索/替换来移除。

预处理器指令最后一个常见用途是调试。用#if DEBUG指令将调试代码包围起来,大多数IDE都支持在发布版中移除这些代码。IDE默认将DEBUG符号用于调试编译,将RELEASE符号用于发布生成。

为了处理else-if条件,可以在#if指令中使用#elif指令,而不是创建两个完全独立的#if块,如代码清单4.56所示。

代码清单4.56 使用#if、#elif和#endif指令

4.10.2 定义预处理器符号

可用两种方式定义预处理器符号。第一种是使用#define指令,如代码清单4.57所示。

代码清单4.57 #define例子

第二种方式是在编译时使用define选项,输出4.27演示了在dotnet命令行上的用法。

输出4.27

多个定义以分号分隔。使用define编译器选项的优点是不需要更改源代码,所以可用相同的源代码文件生成两套不同的二进制程序。

要取消符号定义,可以采取和使用#define相同的方式来使用#undef指令。

4.10.3 生成错误和警告

有时要标记代码中潜在的问题。为此,可以插入#error和#warning指令来分别生成错误和警告消息。代码清单4.58使用井字棋例子,警告代码无法防止玩家多次输入同一步棋。输出4.28展示了结果。

代码清单4.58 用#warning定义警告

输出4.28

包含#warning指令后,编译器会主动发出警告,如输出4.28所示。可用这种警告标记代码中潜在的bug和可能改善的地方。它是提醒开发者任务尚未完结的好帮手。

4.10.4 关闭警告消息

警告指出代码中可能存在的问题,所以很有用。但有的警告可安全地忽略。C# 2.0和之后的编译器提供了预处理器指令#pragma来关闭或还原警告,如代码清单4.59所示。

代码清单4.59 使用预处理器指令#pragma禁用#warning指令

注意,编译器输出时会在警告编号前附加CS前缀。但在用#pragma禁用警告时可以不添加该前缀。

重新启用警告仍是使用#pragma指令,只是需要在warning后添加restore选项,如代码清单4.60所示。

代码清单4.60 使用预处理器指令#pragma还原警告

上述两条指令正好可以将一个特定的代码块包围起来——前提是已知该警告不适用于该代码块。

经常被禁用的警告是CS1591。该警告在使用/doc编译器选项生成XML文档,但并未注释程序中的所有公共项时显示。

4.10.5 nowarn:<warn list>选项

除了#pragma指令,C#编译器通常还支持nowarn:<warn list>选项。它可以获得与#pragma相同的结果,只是不用把它加进源代码,而是把它作为编译器选项使用。除此之外,nowarn选项会影响整个编译过程,而#pragma指令只影响该指令所在的那个文件。例如输出4.29在命令行上关闭了CS0219警告。

输出4.29

4.10.6 指定行号

用#line指令改变C#编译器在报告错误或警告时显示的行号。该指令主要由自动生成C#代码的实用程序和设计器使用。在代码清单4.61中,真实行号显示在最左侧。

代码清单4.61 #line预处理器指令

在上例中,使用#line指令后,编译器会将实际发生在125行的警告报告在113行上发生,如输出4.30所示。

输出4.30

在#line指令后添加default,会反转之前的所有#line的效果,并指示编译器报告真实的行号,而不是之前使用#line指定的行号。

4.10.7 可视编辑器提示

C#提供了只有在可视代码编辑器中才有用的两个预处理器指令:#region和#endregion。像Microsoft Visual Studio这样的代码编辑器能搜索源代码,找到这些指令,并在写代码时提供相应的编辑器功能。C#允许用#region指令声明代码区域。#region和#endregion必须成对使用,两个指令都可选择在指令后跟随一个描述性字符串。此外,可将一个区域嵌套到另一个区域中。

代码清单4.62是井字棋程序的例子。

代码清单4.62 #region和#endregion预处理器指令

Visual Studio检查上述代码,在编辑器左侧提供树形控件来展开和折叠由#region和#endregion指令界定的代码区域,如图4.5所示。

图4.5 Microsoft Visual Studio的折叠区域

4.10.8 启用可空引用类型

第3章中提到过,可以在代码中使用#nullable指令来启用或者禁用C#对启用可空引用类型的支持。#nullable enable启用可空引用类型,而#nullable disable则禁用该类型。此外,#nullable restore将可空引用类型的可用状态恢复为整个工程的全局设置状态。即在工程文件中,通过Nullable节点设置的状态。