C# 预处理器指令
`#if`、`#define`、条件编译、以及 `[Conditional]` 特性。
Key PointsC# 的预处理器只能做条件编译,没有宏替换和文件包含。
常用的几个
C#
#define FEATURE_X // 必须在文件最前面
#nullable enable
class Program
{
static void Main()
{
#if DEBUG
Console.WriteLine("调试模式");
#else
Console.WriteLine("发布模式");
#endif
#if FEATURE_X
Console.WriteLine("功能 X 已启用");
#endif
#if NET8_0_OR_GREATER
Console.WriteLine("运行在 .NET 8 或更高");
#endif
}
}Output
调试模式
功能 X 已启用
运行在 .NET 8 或更高| Directive | Use |
|---|---|
#define / #undef | 定义/取消符号(文件级,必须在最前) |
#if / #elif / #else / #endif | 条件编译 |
#region / #endregion | 折叠代码块(IDE 用) |
#pragma warning | 启用/禁用警告 |
#nullable | 可空检查开关 |
#error / #warning | 主动报错/警告 |
#line | 改行号映射(代码生成用) |
项目级符号
XML
<PropertyGroup>
<DefineConstants>$(DefineConstants);FEATURE_X;TRACE_LOG</DefineConstants>
</PropertyGroup>DEBUG and TRACE come from the build configuration: the Debug profile defines DEBUG, Release does not. Custom symbols belong in the project file so you can switch without touching code.
平台与版本判断
C#
#if NET8_0
Console.WriteLine("正好是 .NET 8");
#endif
#if NET8_0_OR_GREATER
Console.WriteLine("NET8_0_OR_GREATER");
#endif
#if WINDOWS
Console.WriteLine("Windows 目标");
#elif LINUX
Console.WriteLine("Linux 目标");
#endif
// 运行时判断(不是预处理器)
if (OperatingSystem.IsWindows())
{
Console.WriteLine("运行在 Windows");
}Output
正好是 .NET 8
NET8_0_OR_GREATER
Windows 目标
运行在 WindowsCompile-time symbols and runtime checks differ.
#if WINDOWS decides whether code is compiled in; OperatingSystem.IsWindows() decides which branch runs. Only the latter handles cross-platform execution.[Conditional] 特性
C#
using System.Diagnostics;
class Log
{
[Conditional("DEBUG")]
public static void Debug(string msg) => Console.WriteLine($"[DEBUG] {msg}");
public static void Info(string msg) => Console.WriteLine($"[INFO] {msg}");
}
Log.Debug("只在 Debug 配置下输出");
Log.Info("总是输出");Output
[DEBUG] 只在 Debug 配置下输出
[INFO] 总是输出[Conditional] beats #if because the call site needs no directives: in Release the whole call is removed, arguments included. Ideal for logging and assertions.
该不该用
- 该用 —— 平台差异(
#if WINDOWS)、框架版本兼容、调试日志 - 慎用 —— 大量
#if会让代码难以阅读,两条分支可能只测了一条 - 改用 —— 业务开关放配置中心,别用编译期符号
The danger of
#if branches is uneven test coverage: CI may only build Debug, leaving Release-only compile errors unnoticed. Build both in CI.常用的几个
C#
#define FEATURE_X // 必须在文件最前面
#nullable enable
class Program
{
static void Main()
{
#if DEBUG
Console.WriteLine("调试模式");
#else
Console.WriteLine("发布模式");
#endif
#if FEATURE_X
Console.WriteLine("功能 X 已启用");
#endif
#if NET8_0_OR_GREATER
Console.WriteLine("运行在 .NET 8 或更高");
#endif
}
}Output
调试模式
功能 X 已启用
运行在 .NET 8 或更高| 指令 | 用途 |
|---|---|
#define / #undef | 定义/取消符号(文件级,必须在最前) |
#if / #elif / #else / #endif | 条件编译 |
#region / #endregion | 折叠代码块(IDE 用) |
#pragma warning | 启用/禁用警告 |
#nullable | 可空检查开关 |
#error / #warning | 主动报错/警告 |
#line | 改行号映射(代码生成用) |
项目级符号
XML
<PropertyGroup>
<DefineConstants>$(DefineConstants);FEATURE_X;TRACE_LOG</DefineConstants>
</PropertyGroup>DEBUG 和 TRACE 由构建配置自动定义:Debug 配置有 DEBUG,Release 没有。自定义符号写在项目文件里,这样不用改源码就能切换。
平台与版本判断
C#
#if NET8_0
Console.WriteLine("正好是 .NET 8");
#endif
#if NET8_0_OR_GREATER
Console.WriteLine("NET8_0_OR_GREATER");
#endif
#if WINDOWS
Console.WriteLine("Windows 目标");
#elif LINUX
Console.WriteLine("Linux 目标");
#endif
// 运行时判断(不是预处理器)
if (OperatingSystem.IsWindows())
{
Console.WriteLine("运行在 Windows");
}Output
正好是 .NET 8
NET8_0_OR_GREATER
Windows 目标
运行在 Windows编译期符号和运行时判断不是一回事。
#if WINDOWS 决定「这段代码是否被编译进去」;OperatingSystem.IsWindows() 决定「运行时走哪个分支」。前者不能处理跨平台运行,后者可以。[Conditional] 特性
C#
using System.Diagnostics;
class Log
{
[Conditional("DEBUG")]
public static void Debug(string msg) => Console.WriteLine($"[DEBUG] {msg}");
public static void Info(string msg) => Console.WriteLine($"[INFO] {msg}");
}
Log.Debug("只在 Debug 配置下输出");
Log.Info("总是输出");Output
[DEBUG] 只在 Debug 配置下输出
[INFO] 总是输出[Conditional] 比 #if 好在调用处不用写任何指令:Release 构建时编译器会把整个调用删掉,连参数求值都不做。日志、断言这类方法用它最合适。
该不该用
- 该用 —— 平台差异(
#if WINDOWS)、框架版本兼容、调试日志 - 慎用 —— 大量
#if会让代码难以阅读,两条分支可能只测了一条 - 改用 —— 业务开关放配置中心,别用编译期符号
#if 分支最危险的是测试覆盖不全:CI 只跑 Debug,Release 分支里可能有编译错误都没人发现。所以 CI 里应该同时构建 Debug 和 Release。