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 引用类型的注释和警告。指令优先级高于项目设置,效果持续到下一指令或文件结束。

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..."

参数:

  1. 文件名 --- 源文件路径
  2. GUID --- 文件的唯一标识
  3. 校验和字节 --- 十六进制字符串

七、#!​ 和 #: --- 基于文件的应用

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 宏来用。