4、尽量使用条件属性(Conditional Attribute)而不是#if/#endif预处理

本文详细解析了.NET框架中的条件属性(ConditionalAttribute)及其使用场景,对比了与#if/#endif预处理指令的区别,强调了其在不同运行环境下的代码控制优势。

对于这条规则我们一般关心得比较少,只有在系统调试时用得比较多。
      在讲这条规则之前让我们来了解下“什么是条件属性”。

对于.net初学者,当第一次遇到函数上面加一对“[]”中括弧时,一定觉得很陌生。如下所示:

      [assembly: AssemblyTitle("dd")]

.net框架提供了一个特性:属性(Attribute),注意:此属性非彼属性(property)(这都是翻译惹的祸)。
      .net
框架提供了两种类型的Attribute属性:内置属性,自定义属性。顾名思义,内置属性是框架已经为我们开发好的在语言中置入的属性,自定义属性是用户自行定义、创建的属性。其中条件属性(Conditional Attribute)就是属于内置属性。.net框架提供了数百个预先定义好的内置属性,这就不多说了。下面简单的说一下怎么创建用户自定义属性,从而帮助我们理解什么是Attribute属性。

首先要搞清楚:设计实现自定义属性本质就是设计实现一个自定义的类,这个类除了要满足一般类的要求外还要满足以下几个条件:

1、继承System.Attribute;

2、给该类添加AttributeUsageAttribute属性,限制属性类的用法,通过AttributeUsageAttribute类的AttributeTargets参数来指定属性类可以用于何处。如将属性类用于类、结构或者方法等等。

   举例说明:

创建一个新的属性ClassInfo,记录类的创建时间。

ContractedBlock.gifExpandedBlockStart.gifCode
 1 [AttributeUsage(AttributeTargets.Class|AttributeTargets.Struct)]
 2     public class ClassInfoAttribute:Attribute
 3     {
 4         private string _createDate;
 5 
 6         public ClassInfoAttribute(string createDate)
 7         {
 8             _createDate = createDate;
 9         }
10 
11         public string createDate
12         {
13             get
14             {
15                 return _createDate;
16             }
17         }
18     }

    测试类:

ContractedBlock.gifExpandedBlockStart.gifCode
 1 [ClassInfo("2009-08-10")]
 2     public class TClass
 3     {
 4         public string GetAttribute()
 5         {
 6             MemberInfo memInfo = typeof(TClass);
 7             Object[] attributes = memInfo.GetCustomAttributes(
 8                 typeof(ClassInfoAttribute), false);
 9             if (0 != attributes.GetLength(0))
10             {
11                 ClassInfoAttribute item = attributes[0as ClassInfoAttribute;
12                 if (null != item)
13                 {
14                     return item.createDate;
15                 }
16             }
17             return string.Empty;
18         }
19 
20         [Conditional("DEBUG")]
21         public virtual void TestAtDebug()
22         {
23             Console.WriteLine("Test at debuging condition.");
24         }
25 
26         public void Test()
27         {
28             Console.WriteLine("Test at normal condition.");
29         }
30     }

    至此我们应该对Attribute属性大体了解了。下面来看看条件属性(Conditional Attribute)到底是怎么回事。

ContractedBlock.gifExpandedBlockStart.gifCode
 1 [Conditional("DEBUG")]
 2         public virtual void TestAtDebug()
 3         {
 4             Console.WriteLine("Test at debuging condition.");
 5         }
 6 
 7         public void Test()
 8         {
 9             Console.WriteLine("Test at normal condition.");
10         }

   在debug环境下执行的结果是:

Test at debuging condition.
      Test at normal condition.

    在
release环境下执行的结果是:

Test at normal condition.

条件属性是定义方法的运行环境的。条件属性只能在方法上使用。使用条件属性的方法必须符合以下规则:

1、   该方法必须是某个类中的方法;

2、   该方法不能是override方法,但可以是virtual方法。注意:当该方法是virtual方法时,则在派生类中对应的override方法也具有这个属性;

3、   该方法返回的类型必须是void类型;

4、   该方法不能是接口的实现。

上面讲到条件属性只能在方法上使用,所以你应该用条件属性来修饰在不同条件下使用的方法,只有当你要在不同条件下使用一块代码时才用#if/#endif快修饰,即使这样你也应该将该段代码封装到一个方法中。
    再来看个例子:
 

ContractedBlock.gifExpandedBlockStart.gifCode
 1 public class Name
 2     {
 3         private string _firstName;
 4         public string FirstName
 5         {
 6             set
 7             {
 8                 CheckName2(value);
 9                 _firstName = value;
10             }
11         }
12 
13         private void CheckName1(string item)
14         {
15             #if DEBUG
16             Debug.Assert(!string.IsNullOrEmpty(item), "Name cannot be empty!");
17             #endif
18         }
19 
20         [Conditional("DEBUG")]
21         private void CheckName2(string item)
22         {
23             if(string.IsNullOrEmpty(item))
24             Console.WriteLine("Name cannot be empty!");
25         }
26 
27     }


    虽然CheckName1和CheckName2的功能一样,但是你肯定会更愿意选择CheckName2方法。

下面是Release编译的程序集被反编译后的结果:

  #if DEBUG/#endif


    
     [Conditional("DEBUG")]


      #if DEBUG/#endif只有在debug环境下才会被编译、执行。当编译器遇到#if语句后,编译器会检查与编译环境相关的符号是否存在,如果存在,就编译#if块中的代码,如果不存在,编译器会忽略之后的代码直到#endif。当采用Conditional属性时,不管DEBUG环境变量是否被定义,Conditional属性修饰的方法总会被编译到程序集中。这或许看上去是低效的,但这只是占用一点硬盘空间,且该方法不会被载入到内存,更不会被编译成机器代码,除非它被调用。这样做的好处是生成更高效的IL(中间语言Intermediate Language)代码,从而增强程序的可伸缩性,唯一不足的是带来了一点微不足道的硬盘空间开销。

另外,如果过多的在程序块中添加#if/#endif块,使#if/#endif块与普通代码混在一起,容易造成程序结构的混乱,晦涩难懂。并且在调试完之后,为了使用户不会被这些调试信息弄糊涂,还需要把这些#if/#endif一个一个的去掉,这样不仅不方便而且容易引发错误。#if/#endif的这些缺点正好是Conditional属性的优点。至于Conditional属性限制方法的返回类型只能是void类型,不适应返回非void类型的方法,如果你一定要这样做,别忘了可以给方法传递out类型的参数。

总之:Conditional属性跟#if/#endif预处理比起来,具有以下几点优势:

1、  可以由定义标记来灵活的控制;

2、 可以生成更高效的IL代码;

3、 帮助你强制在条件代码上使用更好的结构;

4、可以避免因使用#if/#endif而产生的常见的错误;

     5、能更好的区分条件代码和普通代码。

转载于:https://www.cnblogs.com/JiangSoney/archive/2009/08/10/1543197.html

<think> 嗯,用户想了解#if#endif在编程中的含义。从引用内容看,用户提供了三个参考来源,主要涉及C/C++预处理指令的用法。 用户提供的引用[1]提到#if 0可以解决多行注释嵌套的问题,因为/* */不支持嵌套注释。引用[2]详细说明了两种使用场景:快速注释/测试代码开关和嵌套注释。引用[3]则通过代码示例展示#if 1和#if 0对程序输出的影响。 结合这些引用和自己的知识,准备在回复中涵盖几个要点:首先解释这是预处理指令,由编译器处理;然后说明#if需要与#endif配对使用;重点突出两个典型应用场景:作为条件编译开关和解决注释嵌套问题;最后补充其他用法如配合defined()检查宏定义。 考虑到用户可能刚接触这个概念,会避免深入讨论复杂的条件表达式,而是聚焦基础用法。示例代码会保持简单,演示#if 0/#if 1的切换效果。最后生成的相关问题会围绕实际应用场景展开。 </think>`#if` 和 `endif` 是编程中(尤其在 C、C++、Objective-C 等语言中)使用的**预处理指令(Preprocessor Directives)**。它们的主要功能是进行**条件编译(Conditional Compilation)**,即在编译代码之前,根据指定的条件决定哪些代码块会被编译器处理(编译进最终程序),哪些代码块会被忽略(如同被注释掉一样)。 **核心含义:** 1. **条件编译开关:** `#if` 开启一个条件编译块,`#endif` 标记该块的结束。 2. **基于条件的包含/排除:** 预处理器会评估 `#if` 后面指定的**条件表达式**(通常是常量表达式或检查宏是否定义)。 * 如果条件表达式的结果为 **真(非零)**,那么 `#if` 和 `#endif` 之间的所有代码都会被保留,并交给编译器正常编译。 * 如果条件表达式的结果为 **假(0)**,那么 `#if` 和 `#endif` 之间的所有代码在编译器看来就如同被注释掉一样,会被**完全忽略**,不会包含在最终程序中。 **主要用途和场景:** 1. **快速注释/启用大段代码(代码开关):** 这是最常见的用法之一。 ```c #if 0 // 条件为假 (0), 下面的代码块被“注释掉” // 一大段你暂时不想编译的代码... function_old_version(); complex_debugging_logs(); #endif // 结束被注释的块 #if 1 // 条件为真 (非零),下面的代码块会被编译 function_new_version(); // 当前启用的代码 #endif ``` 通过简单地改变 `#if` 后面的 `0` 或 `1`,你可以快速切换哪些代码块生效,无需手动添加/删除大量的 `//` 或 `/* */`。这在调试、功能切换时非常方便[^1][^2]。 2. **解决“嵌套注释”问题:** C/C++ 的标准注释 `/* ... */` **不能嵌套**。如果试图在一个已注释的块内部再用 `/* ... */` 注释一部分代码,会导致错误(因为第一个 `*/` 会提前结束外层的注释)。使用 `#if 0` 可以完美解决: ```c /* 这是一个外层注释块... 现在想临时注释掉其中一小段代码,但不能用 /* ... */ */ #if 0 // 安全地“注释”掉里面这一部分 ... 原本想用 /* */ 注释掉的代码 ... #endif /* ... 外层注释继续 ... */ ``` `#if 0` 和 `#endif` 可以安全地嵌套在其他 `#if 0` 或 `/* */` 注释块内部[^2]。 3. **根据平台或配置编译不同代码:** 这是条件编译的核心价值。通过检查预定义的宏(如 `_WIN32`, `__linux__`, `DEBUG`),你可以让同一份源代码在不同的操作系统、硬件架构或编译配置(Debug/Release)下编译出不同的行为。 ```c #if defined(_WIN32) // Windows 平台特有的代码 #include <windows.h> WindowsSpecificFunction(); #elif defined(__APPLE__) // macOS 平台特有的代码 #include <CoreFoundation/CoreFoundation.h> MacOSSpecificFunction(); #elif defined(__linux__) // Linux 平台特有的代码 #include <unistd.h> LinuxSpecificFunction(); #endif #ifdef DEBUG // 仅在调试模式下编译的代码(如详细日志、断言检查) printf("Debug info: Value is %d\n", important_value); assert(important_value > 0); #endif ``` 4. **功能模块的启用/禁用:** 在项目配置头文件(如 `config.h`)中定义一些宏(如 `FEATURE_A_ENABLED`),然后在代码中使用 `#if` 来控制相关功能模块是否编译。 ```c // config.h #define USE_OPTIMIZATION 1 // 启用优化 #define ENABLE_FEATURE_X 0 // 禁用特性X // main.c #include "config.h" ... #if USE_OPTIMIZATION optimized_algorithm(); #else standard_algorithm(); #endif #if ENABLE_FEATURE_X feature_x_init(); feature_x_process(); #endif ``` **相关指令:** * `#ifdef` / `#ifndef`:是 `#if defined(...)` / `#if !defined(...)` 的简写,专门用于检查某个宏是否被定义。 * `#elif`:相当于 `else if`,用于在前一个 `#if` 或 `#elif` 条件不满足时检查另一个条件。 * `#else`:相当于 `else`,用于所有前面的 `#if`/`#elif` 条件都不满足时编译的代码块。 **总结:** `#if` 和 `#endif` 是预处理指令,它们共同定义了一个**条件编译块**。预处理器会根据 `#if` 后面的**条件表达式**的真假(非零/零)来决定这个块内的代码是**被编译**还是**被忽略**。其主要用途包括快速注释/启用代码段、安全地实现嵌套注释、根据平台/配置编译不同代码以及控制功能模块的启用/禁用。它们是管理大型项目、提高代码可移植性和可配置性的重要工具[^1][^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值