预处理器指令以 # 开头,看上去像注释,但它直接影响编译过程。最常用的场景是用 #if DEBUG 在调试和发布版本之间切换代码,其次是 #pragma 控制警告。C# 14 还新增了一套面向基于文件的应用的指令,让单文件程序也能引用包和配置 SDK。
- C# 14 新指令 :
#!shebang、#:package、#:sdk等 - 条件编译 :
#if/#elif/#else/#endif,DEBUG 与目标框架符号 - 警告控制 :
#pragma warning disable/restore的正确用法 - 其他常用指令速览 :
#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 永远带上具体的警告编号,作用范围越窄越好。