C# 预处理器指令 — 条件编译、文件应用指令与警告控制

预处理器指令以 #​ 开头,看上去像注释,但它直接影响编译过程。最常用的场景是用 #if DEBUG​ 在调试和发布版本之间切换代码,其次是 #pragma​ 控制警告。C# 14 还新增了一套面向基于文件的应用的指令,让单文件程序也能引用包和配置 SDK。

  1. C# 14 新指令#! shebang、#:package#:sdk
  2. 条件编译#if/#elif/#else/#endif,DEBUG 与目标框架符号
  3. 警告控制#pragma warning disable/restore 的正确用法
  4. 其他常用指令速览#define#region#nullable#line#error#warning

一、基本规则

规则 说明
格式 #开头,独占一行(可含前导空格)
行尾注释 支持(如#pragma warning disable CS0168 // 变量声明但未使用
作用 影响编译过程,不是运行时行为
编译时生效 指令在代码编译前被处理

二、C# 14 新增:基于文件的应用指令

从 C# 14 / .NET 10 开始,单文件应用可以直接在代码中声明依赖和配置,无需 .csproj

2.1 #! --- Shebang(Unix 直接执行)

javascript 复制代码
 #!/usr/bin/env dotnet
 Console.WriteLine("Hello from a standalone script!");

Unix 下配合 chmod +x hello-world.cs​,可以直接 ./hello-world.cs 执行。

2.2 #: --- 文件级配置

引用 NuGet 包

ini 复制代码
 #!/usr/bin/env dotnet
 #:package Spectre.Console@*
 ​
 AnsiConsole.MarkupLine("[bold green]Hello[/] from a file-based app!");
  • @*:拉取最新版本
  • @版本号:固定版本,如 #:package Serilog@3.1.1

引用项目引用

bash 复制代码
 #:project ../SharedLibrary/SharedLibrary.csproj

配置 SDK 和属性

shell 复制代码
 #:sdk Microsoft.NET.Sdk.Web
 #:property PublishAot=false
指令 语法 示例
Shebang #!/usr/bin/env dotnet Unix 直接执行
引用包 #:package 包名@版本 #:package Spectre.Console@*
引用项目 #:project 路径 #:project ../lib/lib.csproj
指定 SDK #:sdk SDK名 #:sdk Microsoft.NET.Sdk.Web
配置属性 #:property 属性=值 #:property PublishAot=false

关键点: #:​ 指令把原本放在 .csproj​ 中的配置搬到了 .cs​ 文件顶部,单文件应用不再需要单独的项目文件。多个包用多条 #:package

三、条件编译

3.1 #if​ / #elif​ / #else​ / #endif

最经典的用法 ------ 调试版和发布版走不同代码路径:

csharp 复制代码
 static void ConfigureLogging()
 {
 #if DEBUG
     Console.WriteLine("Debug logging enabled --- verbose output active.");
 #else
     Console.WriteLine("Release logging --- errors only.");
 #endif
 }
  • DEBUG 符号在 Debug 生成配置下自动定义,Release 下不定义
  • 支持逻辑运算符 &&||!

3.2 目标框架条件

针对不同 .NET 版本编译不同代码:

csharp 复制代码
 static void ShowPlatformInfo()
 {
 #if NET10_0_OR_GREATER
     Console.WriteLine("Running on .NET 10 or later.");
 #elif NET8_0_OR_GREATER
     Console.WriteLine("Running on .NET 8 or 9.");
 #else
     Console.WriteLine("Running on an older .NET version.");
 #endif
 }

常用预定义符号:

符号 含义
DEBUG Debug 生成配置
RELEASE Release 生成配置(如果显式定义)
NET10_0_OR_GREATER .NET 10 及以上
NET8_0_OR_GREATER .NET 8 及以上
NET8_0 精确匹配 .NET 8

3.3 自定义符号

两种方式定义:

csharp 复制代码
 // 方式一:文件顶部用 #define(必须在所有代码之前)
 #define FEATURE_EXPERIMENTAL
 ​
 #if FEATURE_EXPERIMENTAL
     Console.WriteLine("Experimental feature enabled.");
 #endif
csharp 复制代码
 <!-- 方式二:在 .csproj 中通过 DefineConstants 定义 -->
 <PropertyGroup>
     <DefineConstants>FEATURE_EXPERIMENTAL</DefineConstants>
 </PropertyGroup>

划重点: #define 必须在文件最顶部,在任何 using 和代码之前。

四、警告控制:#pragma warning

4.1 基本用法

csharp 复制代码
static void ProcessData()
{
    try
    {
        var data = File.ReadAllText("config.json");
        Console.WriteLine($"Config loaded: {data.Length} characters");
    }
#pragma warning disable CS0168 // Variable is declared but never used
    catch (FileNotFoundException ex)
#pragma warning restore CS0168
    {
        Console.WriteLine("Config file not found, using defaults.");
    }
}

最佳实践:

做法 理由
✅ 明确指定警告号(如CS0168 只抑制你确认无问题的警告
✅ 缩小抑制范围(越窄越好) disable​和restore之间只包必要代码
❌ 全局禁用所有警告 #pragma warning disable​不加编号 = 灾难
❌ 在文件头disable大段代码 掩盖潜在问题的温床

五、其他常用指令速览

指令 语法 用途
#define #define SYMBOL 定义条件编译符号(文件最顶部)
#undef #undef SYMBOL 取消符号定义
#region​/#endregion #region 名称​...#endregion 代码折叠区块(纯 IDE 特性,不影响编译)
#nullable #nullable enable/disable/restore 控制可为 null 引用类型的上下文
#line #line 200 "FileName.cs" 修改编译器报告的行号和文件名
#line hidden #line hidden 调试时不步进该区域代码
#error #error 错误消息 主动生成编译错误
#warning #warning 警告消息 主动生成编译警告

5.1 #region 示例

csharp 复制代码
#region 数据验证
public bool IsValidEmail(string email) => email.Contains('@');
public bool IsValidPhone(string phone) => phone.Length >= 10;
#endregion

IDE 中这段代码可以折叠成一个 数据验证 标签,纯粹为了阅读便利。

5.2 #nullable 示例

csharp 复制代码
#nullable enable
public string? GetOptionalName() => null;  // 可为 null

#nullable disable
public string GetRequiredName() => null;   // 不报警告(nullable 已禁用)

#nullable restore
// 恢复项目默认的 nullable 设置

5.3 #error​ 和 #warning 示例

csharp 复制代码
#if NET8_0
    #error "This library requires .NET 10 or later."
#endif

#if !FEATURE_SECURE
    #warning "Secure feature is not enabled. Proceed with caution."
#endif

常见坑: #region​ 不影响编译,只影响 IDE 折叠。有些人过度使用 #region 来隐藏长方法,真正的问题是方法太长,而不是缺少折叠。

六、指令分类总结

csharp 复制代码
预处理器指令
├── 条件编译
│   ├── #if / #elif / #else / #endif
│   ├── #define / #undef
│   └── 预定义符号 (DEBUG, NET10_0_OR_GREATER...)
│
├── 基于文件的应用 (C# 14)
│   ├── #! (Shebang)
│   └── #:package / #:project / #:sdk / #:property
│
├── 诊断与控制
│   ├── #pragma warning disable / restore
│   ├── #error
│   └── #warning
│
├── 代码组织(仅 IDE)
│   ├── #region / #endregion
│   └── #line / #line hidden
│
└── Nullable 上下文
    └── #nullable enable / disable / restore

最后

预处理器指令最实用的就那三类:条件编译 在实际项目中几乎必用(至少 #if DEBUG​ 会见到),C# 14 的 #:指令 让单文件应用告别手动创建 .csproj​, #pragma ​ 在需要精确控制编译器警告时不可或缺。其他的(#region​、#line​、#error)知道有就行,需要时再查。

记住一条原则: #pragma warning disable永远带上具体的警告编号,作用范围越窄越好。

相关推荐
蝎子莱莱爱打怪13 小时前
零基础用AI写App?兄弟😂 醒醒吧,那只是个玩具罢了!
前端·人工智能·后端
Lucaju14 小时前
做共享目录实时同步,踩过这些坑
java·后端
阿聪谈架构14 小时前
第12章:高级 RAG 技术 —— 让检索更精准、更全面
人工智能·后端
武子康14 小时前
Java-06 深入浅出 MyBatis 数据库1对1模型实战:从概念到查询实现
java·后端
日月云棠14 小时前
4 AbstractStringBuilder —— 可变字符串的骨架实现
java·后端
日月云棠14 小时前
2 Object —— Java 类体系的根节点
java·后端
ping某15 小时前
达梦官方库排查 dmPython 安装后 python -m 报错:.pth + os.execve 的排查实录
后端
moMo15 小时前
前后端模块化分离,web盒子布局思维
前端·后端
程序员牛奶15 小时前
[Algo-3]前缀和秒杀两道区间求和题:一维 + 二维统一模板
后端·算法