if控制代码块是否编译进程序集,删的是“实现”;[conditional]控制方法调用是否编译,删的是“调用点”。前者影响编译输出内容,后者仅移除调用语句,方法本体仍存在。

#if 和 [Conditional] 完全是两回事,别混用
#if 控制代码块是否进入编译输出;[Conditional] 控制对某个方法的调用语句是否被编译进来。前者删的是“实现”,后者删的是“调用点”。
比如你在类库里写了:
[Conditional("TRACE")]<br>void Log(string msg) { Console.WriteLine(msg); }
使用者项目没定义
TRACE,那么所有 Log("x") 调用都会从 IL 里彻底消失——连空调用都不剩。但 Log 方法本身仍存在(只是没被调),而 #if TRACE ... #endif 块里的逻辑,压根不会出现在最终程序集中。-
[Conditional]只能修饰返回void的静态方法 - 同一个方法可加多个
[Conditional],满足任一符号即保留调用 - 若方法有副作用(比如修改字段、发网络请求),千万别用
[Conditional]包裹——调用没了,副作用也没了,容易引发逻辑断裂
#define 必须在文件最顶行,且不能带分号
#define 不是语句,是预处理器指令,不参与语法解析,所以它不能出现在 using 后、命名空间内、类里,也不能加分号。
常见报错:Unexpected preprocessor directive,往往就是因为它被塞进了不该在的位置。
- 正确位置:文件第一行起,连续多行,无缩进、无空行、无其他内容干扰
- 错误示例:
using System;<br>#define DEBUG; // ❌ 分号 + 位置错
- 错误示例:
namespace MyApp {<br> #define MOCK_API // ❌ 不允许在命名空间内 -
#define仅作用于当前文件;全局符号应通过项目属性「生成 → 常量」或 MSBuild 的DefineConstants设置
#if 不支持括号,逻辑结合顺序固定
C# 预处理器不解析括号,#if (DEBUG && RELEASE) 直接报错:Unexpected '('。布尔运算只认 &&、||、!,且按从左到右顺序结合(&& 优先级高于 ||)。
- ✅ 合法:
#if DEBUG && !TEST_MODE - ✅ 合法但易误解:
#if DEBUG || TEST_MODE && LOGGING等价于DEBUG || (TEST_MODE && LOGGING) - ❌ 非法:
#if (DEBUG || TEST_MODE) && LOGGING - 复杂条件必须拆成嵌套:
#if LOGGING<br> #if DEBUG || TEST_MODE<br> Console.WriteLine("...");<br> #endif<br>#endif
漏写 #endif 会静默跳过后续全部代码
编译器不会报错,但漏掉 #endif 会导致其后所有代码(包括 using、类定义、方法)全被跳过——你调试时发现某段逻辑“不见了”,很可能就卡在这儿。
编辑器高亮很关键:VS 中开启「显示所有符号」,Rider 中启用「Preprocessor directives」着色,一眼就能看出结构是否闭合。
- 嵌套层级深时,建议每层都加注释:
#endif // LOGGING - 不要依赖 IDE 自动补全
#endif,它不一定知道你嵌了几层 -
#undef可用于临时取消符号,但极少需要;多数场景下,直接靠项目级符号管理更可靠
真正难处理的是跨程序集行为一致性:库作者用 [Conditional] 暴露钩子,使用者却忘了定义对应符号——调用没了,日志/监控/诊断全失效,而且毫无运行时提示。这比语法错误更隐蔽。











