Skip to content

C# - 预处理器指令

预处理器指令是 C# 编译器的特殊指令,它们在源代码实际编译开始之前进行处理。所有预处理器指令都以 # 符号开头,并且必须是行上的唯一指令。与 C++ 中的预处理器不同,C# 指令不用于创建宏,主要用于条件编译、代码组织以及影响编译器警告和错误。

条件指令允许你根据是否定义了某些符号来包含或排除代码的某些部分进行编译。这对于创建不同的构建配置(例如 DEBUG 和 RELEASE)非常有用。

符号可以在文件顶部使用 #define 定义,或者更常见的是在项目的构建设置(.csproj 文件)中定义。Visual Studio 会自动为 Debug 构建定义 DEBUG 符号。

// 要测试此功能,你可以在 .csproj 文件或此文件顶部定义该符号:
// #define FEATURE_PREMIUM
using System;
public class Program
{
public static void Main()
{
Console.WriteLine("Welcome to the application!");
#if FEATURE_PREMIUM
Console.WriteLine("Premium features are enabled.");
ConnectToPremiumService();
#elif DEBUG
Console.WriteLine("Running in DEBUG mode. No premium features.");
#else
Console.WriteLine("Running in RELEASE mode.");
#endif
Console.WriteLine("Application finished.");
}
#if FEATURE_PREMIUM
public static void ConnectToPremiumService()
{
// 高级功能的逻辑将在此处
Console.WriteLine("--> Connecting to premium backend...");
}
#endif
}

你可以使用逻辑运算符 ==、!=、&& 和 || 来创建更复杂的条件。

#define 指令定义一个符号。它的作用域仅限于定义它的文件。#undef 指令则移除一个符号的定义。

#define EXPERIMENTAL_FEATURE
#if EXPERIMENTAL_FEATURE
Console.WriteLine("Experimental code is included.");
#endif
#undef EXPERIMENTAL_FEATURE
#if EXPERIMENTAL_FEATURE
// 此块将不会被编译,因为该符号已被取消定义。
Console.WriteLine("This will never be printed.");
#endif

最佳实践:对于项目范围的符号,例如 DEBUG 或 RELEASE,最好在项目设置(.csproj 文件中的 <DefineConstants>)中定义它们,而不是在每个文件中使用 #define。

#region 和 #endregion 指令允许你在 Visual Studio 或 JetBrains Rider 等 IDE 中定义可折叠的代码块。这有助于将长文件组织成逻辑部分。

using System;
public class DataService
{
#region Public Properties
public string ConnectionString { get; set; }
public int Timeout { get; set; }
#endregion
#region Public Methods
public void Connect()
{
// ...
}
public void Disconnect()
{
// ...
}
#endregion
}

注意:虽然 #region 可能有用,但过度使用它可能表明一个类过大且职责过多。通常,将一个大类重构为更小、更集中的类比将其复杂性隐藏在区域内部更好。

控制编译器输出:#warning 和 #error

Section titled “控制编译器输出:#warning 和 #error”

这些指令允许你从代码中的特定位置生成自定义编译器警告和错误。

  • #warning:生成 CS1030 级别一的警告。编译会成功。
  • #error:生成 CS1029 错误并立即停止编译。
public class LegacyApiWrapper
{
public void OldMethod()
{
#warning "This method is obsolete and will be removed in the next version. Use NewMethod() instead."
// ...
}
public void CriticalMethod()
{
#if !NET6_0_OR_GREATER
#error "This method requires .NET 6 or newer to compile."
#endif
// ...
}
}

#nullable 指令对于现代 C# 开发至关重要,因为它设置了可空批注上下文(nullable annotation context)和可空警告上下文(nullable warning context)。此功能通过让你更明确地指出引用类型是否可以为 null,从而帮助你编写更安全的代码。

  • #nullable enable:启用可空引用类型功能。如果你将 null 赋值给非可空变量(例如 string),或者解引用(dereference)一个可能为 null 的变量(例如 string?),编译器将发出警告。
  • #nullable disable:禁用此功能,恢复到 C# 8.0 之前的行为,即任何引用类型都可以为 null 而不发出警告。
  • #nullable restore:将上下文恢复到项目级别设置。
// 最好在你的 .csproj 文件中项目范围启用此功能:
// <Nullable>enable</Nullable>
#nullable enable // 激活此代码块的可空上下文
public class UserProfile
{
public string Name { get; set; } // 非可空:不得为 null
public string? MiddleName { get; set; } // 可空:可以为 null
public UserProfile(string name)
{
Name = name;
}
public void PrintNameLength()
{
Console.WriteLine($"Name length: {Name.Length}"); // 安全访问
// Console.WriteLine($"Middle name length: {MiddleName.Length}"); // 编译器警告:MiddleName 可能为 null!
if (MiddleName != null)
{
Console.WriteLine($"Middle name length: {MiddleName.Length}"); // 检查后安全
}
}
}

#line 指令修改编译器在警告和错误中报告的行号,以及可选地修改文件名。这主要由代码生成工具使用,用于将生成的 C# 代码映射回原始源文件(例如,由 Razor .cshtml 文件生成的 C# 代码)。