C# 预处理器指令的完整语言参考 ,涵盖所有指令的精确语法、使用规则和边界条件。与前一篇教程风格的文章不同,这篇侧重语法规范和细节,适合需要精确查阅特定指令的开发者。
核心前提: C# 的预处理器指令没有独立的预处理器,由编译器直接处理。指令必须独占一行。不能像 C/C++ 那样创建宏,条件编译仅判断符号是否定义(布尔逻辑)。
| 指令类别 | 涵盖指令 |
|---|---|
| Nullable 上下文 | #nullable enable/disable/restore及 annotations/warnings 子集 |
| 条件编译 | #if、#elif、#else、#endif |
| 符号定义 | #define、#undef |
| 代码区域 | #region、#endregion |
| 诊断信息 | #error、#warning、#line |
| 编译器指令 | #pragma warning、#pragma checksum |
| 文件应用 | #!(shebang)、#: |
一、#nullable --- 可为空上下文
控制可为 null 引用类型的注释和警告。指令优先级高于项目设置,效果持续到下一指令或文件结束。
1.1 基本形式
| 指令 | 效果 |
|---|---|
#nullable disable |
禁用可为空上下文(注释 + 警告) |
#nullable enable |
启用可为空上下文 |
#nullable restore |
恢复为项目级别的设置 |
1.2 细粒度控制
可以单独控制注释 (annotations,即 ? 标记)和警告(warnings):
| 指令 | 效果 |
|---|---|
#nullable disable annotations |
禁用?标记(不检查 nullability 注释) |
#nullable enable annotations |
启用?标记 |
#nullable restore annotations |
恢复注释设置为项目级别 |
#nullable disable warnings |
禁用可为 null 警告 |
#nullable enable warnings |
启用可为 null 警告 |
#nullable restore warnings |
恢复警告设置为项目级别 |
使用场景: 在一个项目逐步迁移到 nullable aware 时,可以局部启用或禁用特定文件/区域。
csharp
// 整个文件默认启用 nullable
#nullable enable
public string? GetOptional() => null; // string? 有效,返回 null 不报警告
#nullable disable warnings
public string GetRequired() => null; // 返回 null 不报警告(warnings 已禁用)
#nullable restore warnings
// 后面的代码恢复警告
二、条件编译 --- #if / #elif / #else / #endif
2.1 基本语法
csharp
#if SYMBOL
// 当 SYMBOL 已定义时编译
#elif OTHER_SYMBOL
// 当 OTHER_SYMBOL 已定义且前面条件不满足时编译
#else
// 前面所有条件都不满足时编译
#endif
2.2 支持的运算符
| 运算符 | 含义 | 示例 |
|---|---|---|
! |
逻辑非 | #if !DEBUG |
== |
相等 | #if NET8_0 == true |
!= |
不等 | #if NET8_0 != true |
&& |
逻辑与 | #if DEBUG && NET10_0 |
| ` | ` | |
() |
分组 | `#if (DEBUG |
2.3 预定义符号
SDK 风格项目根据目标框架自动定义一组符号:
桌面 / 框架相关:
| 目标框架 | 精确版本符号 | 范围符号 |
|---|---|---|
| .NET Framework 4.7.2 | NET472 |
NET472_OR_GREATER |
| .NET Framework 4.8 | NET48 |
NET48_OR_GREATER |
| .NET Standard 2.1 | NETSTANDARD2_1 |
NETSTANDARD2_1_OR_GREATER |
现代 .NET(.NET 5+):
| 目标框架 | 精确版本符号 | 范围符号 |
|---|---|---|
| .NET 8 | NET8_0 |
NET8_0_OR_GREATER |
| .NET 9 | NET9_0 |
NET9_0_OR_GREATER |
| .NET 10 | NET10_0 |
NET10_0_OR_GREATER |
平台符号:
| 平台 | 符号 | 带版本号 |
|---|---|---|
| Android | ANDROID |
ANDROID35_0_OR_GREATER |
| iOS | IOS |
IOS15_1_OR_GREATER |
| Windows | WINDOWS |
WINDOWS10_0_17763_0_OR_GREATER |
通用符号:
| 符号 | 来源 |
|---|---|
DEBUG |
Debug 生成配置自动定义 |
TRACE |
Trace 常量,默认开启 |
2.4 多框架适配示例
csharp
public static string DownloadContent(string url)
{
#if NET40
WebClient _client = new WebClient();
return _client.DownloadString(url);
#else
HttpClient _client = new HttpClient();
return _client.GetStringAsync(url).Result;
#endif
}
三、符号定义 --- #define / #undef
3.1 语法
csharp
#define MYTEST // 定义符号
#undef MYTEST // 取消定义
3.2 关键规则
| 规则 | 说明 |
|---|---|
| 位置 | 必须在文件最开头,在所有非预处理器代码之前 |
| 作用域 | 从定义位置到文件末尾 |
| 不能赋值 | #define MYTEST 1是非法的,只能声明符号名 |
| 常量应用 | 需要常量值用const,不要用#define |
| 编译器选项 | 也可通过DefineConstants属性全局定义 |
csharp
// 正确 ✅
#define FEATURE_EXPERIMENTAL
// 错误 ❌
#define FEATURE_EXPERIMENTAL true // 编译错误
四、#region / #endregion --- 代码折叠 ⭐
4.1 语法
csharp
#region 区域名称
// ... 可折叠的代码 ...
#endregion
4.2 规则
| 规则 | 说明 |
|---|---|
| 配对 | #region必须由#endregion终止 |
| 嵌套 | #region内可以嵌套另一个#region |
与#if的关系 |
不能重叠 ,但可互相嵌套:#region内含#if块,或#if内含#region |
| 编译影响 | 不影响编译,纯 IDE 大纲视图特性 |
4.3 正确 vs 错误的嵌套
csharp
// ✅ 正确 --- #if 完整包含在 #region 内
#region 多框架适配
#if NET8_0_OR_GREATER
Console.WriteLine(".NET 8+");
#endif
#endregion
// ✅ 正确 --- #region 完整包含在 #if 内
#if DEBUG
#region 调试工具
Console.WriteLine("Debug tools active");
#endregion
#endif
// ❌ 错误 --- 重叠(编译失败)
#region 开始
#if DEBUG
#endregion // ❌ 在 #if 没有 #endif 之前关闭 #region
#endif
4.4 示例
csharp
#region MyClass definition
public class MyClass
{
static void Main()
{
}
}
#endregion
常见坑:
#region 不能拆分#if/#endif 块。如果你在一个#region 内打开了#if,必须在同一个#region 内用#endif关闭。
五、诊断指令 --- #error、#warning、#line
5.1 #error --- 主动生成编译错误
csharp
#error Deprecated code in this method.
// 编译错误 CS1029: #error: 'Deprecated code in this method.'
常用场景: 在不支持的条件下阻止编译:
csharp
#if !NET8_0_OR_GREATER
#error This library requires .NET 8 or later.
#endif
5.2 #warning --- 主动生成编译警告
csharp
#warning Deprecated code in this method.
// 编译警告 CS1030: #warning: 'Deprecated code in this method.'
5.3 #line --- 修改编译输出行号/文件名
| 指令 | 效果 |
|---|---|
#line 200 "Special.cs" |
强制编译器以第 200 行、"Special.cs" 文件名报告后续代码 |
#line default |
恢复默认行号 |
#line hidden |
对调试器隐藏后续行(逐步执行时跳过) |
C# 高级语法(适用于 DSL / 代码生成器):
csharp
#line (起始行, 起始列) - (结束行, 结束列) 列偏移 "原始源文件名"
常用场景:Razor 页面将生成的 .cs 文件中警告/错误映射回 .cshtml 原始源代码。
六、#pragma 指令
6.1 #pragma warning --- 警告控制
csharp
#pragma warning disable 414, CS3021 // 从下一行起禁用指定警告
// ... 受抑制的代码 ...
#pragma warning restore CS3021 // 恢复指定警告
格式控制:
csharp
#pragma warning disable format // 禁用代码格式化(如 Ctrl+K,D)
// ... 你想保留手动格式的代码 ...
#pragma warning restore format // 恢复格式化
6.2 #pragma checksum --- 调试校验和
主要为 ASP.NET 页面设计,确保调试器找到正确的源文件:
csharp
#pragma checksum "file.cs" "{3673e4ca-6098-4ec1-890f-8fceb2a794a2}" "hex-bytes..."
参数:
- 文件名 --- 源文件路径
- GUID --- 文件的唯一标识
- 校验和字节 --- 十六进制字符串
七、#! 和 #: --- 基于文件的应用
7.1 #! --- Shebang
csharp
#!/usr/bin/env dotnet
Console.WriteLine("Hello");
Unix 下配合 chmod +x 可直接执行。
7.2 #: --- 文件级配置
C# 编译器忽略 #: 开头的行,但它们被 .NET SDK 等工具按约定解析:
csharp
#:package Spectre.Console@*
#:sdk Microsoft.NET.Sdk.Web
#:property PublishAot=false
八、指令不重叠关系速查
| 结构 A | 结构 B | 可以嵌套? | 条件 |
|---|---|---|---|
#region |
#region |
✅ | 完整嵌套 |
#if |
#if |
✅ | 完整嵌套 |
#region |
#if |
✅ | 一个完整包含另一个 |
#region |
#if |
❌ | 不能重叠/交错 |
最后
这份参考文档覆盖了 C# 预处理器指令的全部语法规范 。日常开发中最常用的就三类:#if DEBUG、#nullable enable、#region。其余的(#line 高级映射、#pragma checksum)主要在代码生成器和 ASP.NET 底层框架中使用。记住最核心的一条:C# 预处理指令没有宏,条件编译只判断符号有没有定义,不要把它当成 C/C++ 的 #define 宏来用。