目录
[1. 什么是编译器?](#1. 什么是编译器?)
[2. 什么是预处理器指令?](#2. 什么是预处理器指令?)
[3. C# 中常见的预处理器指令](# 中常见的预处理器指令)
[(1) #define 和 #undef](#define 和 #undef)
[(2) 条件编译指令(#if, #elif, #else, #endif)](#if, #elif, #else, #endif))
[注意哈:C# 的预处理器没有 #elseif,正确写法是 #elif(注意是 #elif 不是 #elseif!)。这些玩意一定是配套出现的,例如出现了#if,就一定会有#endif](# 的预处理器没有 #elseif,正确写法是 #elif(注意是 #elif 不是 #elseif!)。这些玩意一定是配套出现的,例如出现了#if,就一定会有#endif)
[(3) #warning 和 #error](#warning 和 #error)
[示例 1:修改行号和文件名](#示例 1:修改行号和文件名)
[示例 2:隐藏生成的代码](#示例 2:隐藏生成的代码)
[(6) #region 和 #endregion](#region 和 #endregion)
1. 什么是编译器?
编译器是一种将高级编程语言代码 (如 C#、Java、Python)翻译成计算机可执行代码(如机器码或中间语言)的程序。它的核心作用包括:
-
语法检查:验证代码是否符合语言规范。
-
优化:提高代码的运行效率(如减少冗余计算)。
-
生成目标代码 :输出可执行文件(如
.exe
或.dll
)。
在 C# 中,编译器(如 csc.exe
)将源代码转换为中间语言(IL),再由 .NET 运行时(CLR)通过 JIT 编译器转换为机器码执行。
2. 什么是预处理器指令?
预处理器指令是在编译前由编译器处理的特殊指令,用于在编译阶段控制代码的包含、排除或条件编译。它们:
-
不参与程序运行,仅在编译时生效。
-
以
#
符号开头 (如#if
、#define
)。 -
不改变代码逻辑,而是控制哪些代码被编译。
与 C/C++ 不同,C# 的预处理器不支持宏定义 (如 #define PI 3.14
),功能较为简化。
3. C# 中常见的预处理器指令
(1) #define
和 #undef
-
作用:定义或取消定义一个符号(Symbol),用于条件编译。
-
#define
:在代码文件中定义一个符号(Symbol),仅用于条件编译判断(不是变量!)。 -
#undef
:取消之前定义的符号。 -
示例:
cs
#define DEBUG // 定义 DEBUG 符号(从这行开始生效)
#undef DEBUG // 取消 DEBUG 符号(从这行开始失效)
using System;
class Program {
static void Main() {
#if DEBUG
Console.WriteLine("调试模式"); // 这行代码不会编译
#endif
}
}
实际使用:所以其实他的作用就是使得某些代码不被执行
cs
#define WINDOWS // 定义 WINDOWS 符号
//#define LINUX // 注释掉 LINUX 符号
public class Program {
public static void Main() {
#if WINDOWS
Console.WriteLine("运行 Windows 专用逻辑");
#elif LINUX
Console.WriteLine("运行 Linux 专用逻辑");
#else
Console.WriteLine("未知平台");
#endif
}
}
若想切换平台,只需注释
#define WINDOWS
,取消注释#define LINUX
。
注意:
必须在文件顶部使用 :
#define
和#undef
必须放在所有代码之前(比如using
语句之前)。符号无具体值 :符号只是"存在"或"不存在",不能赋值(如
#define VERSION 1
是错误语法!)。作用域为当前文件:每个文件的符号定义是独立的,除非通过项目全局定义。
(2) 条件编译指令(#if
, #elif
, #else
, #endif
)
-
作用 :根据符号是否被定义,控制哪些代码会被编译器包含 。
完全不同于运行时的if-else
!条件编译的代码在编译时就被决定是否保留。 -
示例:
cs
#define DEBUG
#define LOGGING
public class Program {
public static void Main() {
#if DEBUG && LOGGING
Console.WriteLine("调试模式 + 日志开启");
#elif DEBUG
Console.WriteLine("仅调试模式");
#else
Console.WriteLine("发布模式");
#endif
}
}
- 运算符支持:
&&
(与)、||
(或)、!
(非),例如#if !RELEASE
注意哈:
C# 的预处理器没有 #elseif
,正确写法是 #elif
(注意是 #elif
不是 #elseif
!)。这些玩意一定是配套出现的,例如出现了#if,就一定会有#endif
**注意:**条件编译 vs. 运行时 if
条件编译的代码在编译后不存在 ,而 if
是运行时判断:
cs
#if DEBUG
Console.WriteLine("调试模式"); // 编译后可能被移除
#endif
if (isDebug)
{
Console.WriteLine("调试模式"); // 始终存在于编译结果中
}
(3) #warning
和 #error
-
作用 :
#warning
:在编译时生成自定义警告 ,用于提示开发者注意某些问题(但不会阻止编译)。#error
:在编译时生成自定义错误,强制编译失败(用于阻止不符合条件的代码编译)。 -
示例:
cs
public class PaymentService {
public void ProcessPayment() {
#warning TODO: 需要实现支付逻辑
// 临时占位代码
Console.WriteLine("支付功能待实现");
}
}
cs
#if !NET6_0
#error 此代码必须使用 .NET 6.0 或更高版本编译
#endif
public class Program {
public static void Main() {
// ...
}
}
如果项目目标框架不是 .NET 6.0,编译会直接失败,并显示错误信息。

(4) #line(不太重要)
-
作用 :修改编译器报告的行号和文件名 :常用于代码生成工具(如 Razor 模板、T4 模板),将错误定位到原始文件而非生成的中间文件。隐藏代码块 :结合
#line hidden
和#line default
控制调试器的可见性。
示例 1:修改行号和文件名
cs
#line 200 "SpecialFile.cs"
public class FakeClass {
// 故意写一个错误
public void Method() {
int x = "error"; // 这里会报错
}
}
#line default // 恢复原始行号和文件名
SpecialFile.cs(200,13): error CS0029: 无法将类型"string"隐式转换为"int"
-
调试时,错误会显示在
SpecialFile.cs
的第 200 行(实际文件可能不存在)。
示例 2:隐藏生成的代码
cs
// 生成的代码开始
#line hidden
public class GeneratedClass {
public void AutoGeneratedMethod() { /*...*/ }
}
#line default
// 生成的代码结束
- 调试时 :
GeneratedClass
的代码在 IDE 中会被折叠或隐藏,直接跳过。
(5) #pragma
-
作用 :禁用/恢复警告 :临时屏蔽不需要的编译器警告。优化代码 :指示编译器对代码块进行优化(如
#pragma optimize
)。
示例1:
cs
public class Example {
public void Demo() {
#pragma warning disable CS0168 // 禁用 "变量未使用" 的警告
int unusedVariable;
#pragma warning restore CS0168 // 恢复警告
int usedVariable = 10;
Console.WriteLine(usedVariable);
}
}
示例2:
cs
#pragma warning disable CS0219, CS8602 // 禁用 "变量已赋值但未使用" 和 "可能为 null 的引用" 警告
public class Test {
public void Method() {
int x = 5; // 不会触发 CS0219
string s = null;
Console.WriteLine(s.Length); // 不会触发 CS8602
}
}
#pragma warning restore CS0219, CS8602
示例3:
cs
#pragma optimize off // 关闭优化
public void CriticalMethod() {
// 此方法内的代码不会被编译器优化
}
#pragma optimize on
用于调试时保持代码结构不被优化器破坏。
(6) #region
和 #endregion
-
作用:标记代码块(对编译器无影响,仅用于 IDE 代码折叠)。这个你在IDE里面写的时候,左边会出现一个+号你就可以折叠起来了。
-
示例:
cs
#region 初始化逻辑
void Initialize() {
// ...
}
#endregion
4、总结
指令 | 作用 | 示例 |
---|---|---|
#define SYMBOL |
定义符号,用于条件编译 | #define DEBUG |
#undef SYMBOL |
取消定义的符号 | #undef DEBUG |
#if #elif #else #endif |
根据符号条件编译代码块 | #if DEBUG Console.WriteLine("调试模式"); #endif |
#warning MESSAGE |
在编译时生成警告(提示开发者注意问题) | #warning TODO: 需要优化此方法 |
#error MESSAGE |
在编译时生成错误(强制编译失败) | #error 此代码需要 .NET 6.0 |
#line N "FILE" |
修改编译器输出的行号和文件名 | #line 100 "Generated.cs" |
#pragma |
控制编译器行为(如禁用警告、代码优化) | #pragma warning disable CS0168 |
#region #endregion |
标记代码块(仅用于 IDE 折叠显示,无编译影响) | #region 初始化逻辑 void Init() {} #endregion |
完整一个版本代码示例:
cs
#define DEBUG // 定义 DEBUG 符号
//#define NET6_0 // 取消注释模拟 .NET 6.0 环境
#undef RELEASE // 取消 RELEASE 符号(如果存在)
using System;
#pragma warning disable CS0168 // 禁用未使用变量警告
#region 主程序
public class Program
{
public static void Main()
{
// 条件编译示例
#if DEBUG
Console.WriteLine("===== 调试模式 =====");
#warning 注意:调试模式下日志会输出敏感信息!
#elif RELEASE
Console.WriteLine("===== 发布模式 =====");
#else
#error 未定义编译模式(DEBUG 或 RELEASE)
#endif
// 平台逻辑示例
#if WINDOWS
Console.WriteLine("运行 Windows 专用代码");
#elif LINUX
Console.WriteLine("运行 Linux 专用代码");
#else
Console.WriteLine("未知平台");
#endif
// #pragma 示例
int unusedVar; // 不会触发 CS0168 警告
Console.WriteLine("Hello World");
// #line 示例(模拟代码生成工具)
#line 200 "MagicFile.cs"
// 故意写一个错误(编译时会显示在 MagicFile.cs 第 200 行)
// string s = 123; // 取消注释会报错
#line default
}
}
#pragma warning restore CS0168
#endregion