C# 预处理器指令语言

 更新时间:2026年06月20日 09:16:59   作者:叫我少年  
本文主要介绍了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 时,可以局部启用或禁用特定文件/区域。

 // 整个文件默认启用 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

通用符号:

符号来源
​DEBUGDebug 生成配置自动定义
​TRACETrace 常量,默认开启

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   // 编译错误

四、#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..."

参数:

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

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

7.1#!— Shebang

 #!/usr/bin/env dotnet
 Console.WriteLine("Hello");

Unix 下配合 chmod +x 可直接执行。

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++ 的 #define 宏来用。

到此这篇关于C# 预处理器指令语言的文章就介绍到这了,更多相关C# 预处理器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论