《Effective C#》读书笔记——条目4:使用Conditional特性而不是#if条件编译<C#语言习惯>
#if/#endif 语句常用来基于同一份源码生成不同的编译结果,其中最常见的就是debug版和release版。但是这些工具在实际应用中并不是非常友好,因为它们容易被滥用,其代码页进而难以理解或调试。C#设计中考虑到这个问题,并提供了更好的工具——Conditional特性,用来为不同的环境编译不同的机器码。Conditional特性适用于方法的层面,这将强制我们将条件代码拆分为独立的方法。在需要编写条件代码时,我们应该使用Conditional特性来替代#if/#endif。
使用#if/#endif 语句的缺点
例如编写一个私有方法来获取调用它的函数名称:
1 private string CheckMethod() 2 { 3 4 #if DEBUG 5 Trace.WriteLine("Entering CheckState for Person"); 6 7 string methodName = new StackTrace().GetFrame(1).GetMethod().Name; 8 9 return methodName; 10 #endif 11 return null; 12 }
条件编译#if和#endif将会在最终release版本中留下一个名为CheckMethod()的空方法,但它在release版和debug版中都将被调用,虽然在release版本中CheckMethod()什么也不做,但是方法的加载、JIT编译和调用仍旧有些开销。而且这也容易引入一些问题:
1 public void Func() 2 { 3 string msg = null; 4 5 #if DEBUG 6 msg = "Debug"; 7 #endif 8 Console.WriteLine(msg); 9 }
这段代码在Debug版本中不会有问题,但是在release版本中就会输出一个空行,出错的原因是因为我们把属于主程序的逻辑和条件编译的逻辑混合在一起了。在源代码中随意使用#if和#endif将让你很难诊断出不同版本之间的差异。
更好的解决办法——使用Conditional特性
为了避免出现上面的问题我们可以使用Conditional特性。使用Conditional特性即可将一些函数拆分出来,让其只有在定义了某些环境变量或者设置了某个值之后才能编译并成为类的一部分。Conditional特性最常用的地方就是讲一段代码变成调试语句。使用Conditional特性的隔离策略要比#if/#endif不容易出错。
看下面的代码:
1 [Conditional("DEBUG")] 2 private void CheckMethod() 3 { 4 Trace.WriteLine("Entering CheckState for Person"); 5 string methodName = new StackTrace().GetFrame(1).GetMethod().Name; 6 }
无论是否定义了DEBUG环境变量,CheckMethod()方法都将被编译到程序集中。但是如果没有被调用,CheckMethod()方法并不会被加载到内存中,也不会被JIT编译。这其实也揭示了C#编译器的编译过程与JIT编译座次之间的区别。
Conditional特性的限制
Conditional特性只可以应用在整个方法上。
任何使用了Conditional特性的方法都只能返回void类型。
小节:
综上所述,使用Conditional特性生成的IL要比使用#if/#endif时更有效率。同时,将其限制在函数层面上可以更清晰的将条件性代码分离出来,以便进一步保证代码的良好结构。此外C#编译器也为此提供良好的支持,从而避免了以前使用#if/#endif时常犯的错误。与预处理指令相比,Conditional特性让我们可以更好地将条件性代码分离开来。
阅读书目:《Effective C#》