C#预处理器指令语言
C#的预处理器指令由编译器直接处理,没有独立的预处理器。主要用于条件编译、可为空上下文控制、代码折叠等场景。支持定义符号、条件判断、可为空上下文、区域折叠、错误提示、编译指示等指令。条件编译仅判断符号是否定义,不包含宏替换功能。
C# 预处理器指令——听起来像是个冷门话题,但实际在条件编译、代码折叠、可为空上下文控制这些日常场景里,几乎每个 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 引用类型的注释和警告。它的优先级高于项目设置,一旦设定,效果会持续到遇到下一条指令,或者文件结束。实际迁移旧项目时,经常靠它局部开启或关闭 nullable 检查。
1.1 基本形式
| 指令 | 效果 |
|---|---|
| #nullable disable | 禁用可为空上下文(注释 + 警告) |
| #nullable enable | 启用可为空上下文 |
| #nullable restore | 恢复为项目级别的设置 |
1.2 细粒度控制
有时候你只想控制注释(也就是 ? 标记),或者只想控制警告,那就用下面这些子指令:
| 指令 | 效果 |
|---|---|
| #nullable disable annotations | 禁用 ? 标记(不检查 nullability 注释) |
| #nullable enable annotations | 启用 ? 标记 |
| #nullable restore annotations | 恢复注释设置为项目级别 |
| #nullable disable warnings | 禁用可为 null 警告 |
| #nullable enable warnings | 启用可为 null 警告 |
| #nullable restore warnings | 恢复警告设置为项目级别 |
典型场景: 当项目逐步迁移到 nullable aware 时,可以在特定文件或代码区域临时禁用或启用,避免一次改动整个项目。
// 整个文件默认启用 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 基本语法
#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 || TRACE |
| () | 分组 | #if (DEBUG || TRACE) && !PRODUCTION |
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 多框架适配示例
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 语法
#define MYTEST // 定义符号 #undef MYTEST // 取消定义
3.2 关键规则
| 规则 | 说明 |
|---|---|
| 位置 | 必须在文件最开头,在所有非预处理器代码之前 |
| 作用域 | 从定义位置到文件末尾 |
| 不能赋值 | #define MYTEST 1 是非法的,只能声明符号名 |
| 常量应用 | 需要常量值用 const,不要用 #define |
| 编译器选项 | 也可通过 DefineConstants 属性全局定义 |
// 正确 ✅ #define FEATURE_EXPERIMENTAL // 错误 ❌ #define FEATURE_EXPERIMENTAL true // 编译错误
这一点和 C/C++ 完全不同,千万别弄混。
四、#region / #endregion——代码折叠
4.1 语法
#region 区域名称 // ... 可折叠的代码 ... #endregion
4.2 规则
| 规则 | 说明 |
|---|---|
| 配对 | #region 必须由 #endregion 终止 |
| 嵌套 | #region 内可以嵌套另一个 #region |
| 与 #if 的关系 | 不能重叠,但可互相嵌套:#region 内含 #if 块,或 #if 内含 #region |
| 编译影响 | 不影响编译,纯 IDE 大纲视图特性 |
4.3 正确 vs 错误的嵌套
// ✅ 正确 — #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 示例
#region MyClass definition
public class MyClass
{
static void Main()
{
}
}
#endregion
常见坑: #region 不能拆分 #if/#endif 块。如果你在一个 #region 内打开了 #if,必须在同一个 #region 内用 #endif 关闭。跨区域交叉是不允许的。
五、诊断指令——#error、#warning、#line
5.1 #error——主动生成编译错误
#error Deprecated code in this method. // 编译错误 CS1029: #error: 'Deprecated code in this method.'
常用场景: 在不支持的条件下直接阻止编译,比如让旧框架的调用直接报错。
#if !NET8_0_OR_GREATER
#error This library requires .NET 8 or later.
#endif
5.2 #warning——主动生成编译警告
#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 / 代码生成器):
#line (起始行, 起始列) - (结束行, 结束列) 列偏移 "原始源文件名"
这个高级语法常见于 Razor 页面,编译器把生成的 .cs 文件中的错误映射回原始的 .cshtml 文件,方便开发者定位。
六、#pragma 指令
6.1 #pragma warning——警告控制
#pragma warning disable 414, CS3021 // 从下一行起禁用指定警告 // ... 受抑制的代码 ... #pragma warning restore CS3021 // 恢复指定警告
格式控制:
#pragma warning disable format // 禁用代码格式化(如 Ctrl+K,D) // ... 你想保留手动格式的代码 ... #pragma warning restore format // 恢复格式化
6.2 #pragma checksum——调试校验和
这主要服务于 ASP.NET 页面,用于确保调试器能找到正确的源文件:
#pragma checksum "file.cs" "{3673e4ca-6098-4ec1-890f-8fceb2a794a2}" "hex-bytes..."
参数:
- 文件名 ——源文件路径
- GUID ——文件的唯一标识
- 校验和字节 ——十六进制字符串
这个指令日常开发很少手动去写,但了解它对理解 ASP.NET 的调试机制有帮助。
七、#! 和 #:——基于文件的应用
7.1 #!——Shebang
#!/usr/bin/env dotnet
Console.WriteLine("Hello");
在 Unix 系统下,配合 chmod +x 可以直接把 .cs 文件当脚本执行。有点类似 Python 脚本的 shebang 行。
7.2 #:——文件级配置
C# 编译器会忽略 #: 开头的行,但 .NET SDK 等工具会按约定解析它们:
#: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++ 的宏来用。
Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。
极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。
















