#if/#endif块用来对同样的源代码产生不同的版本,大多是debug和release版本。但它并不好用,#if/#endif块很容易被滥用,代码难于调试与理解。语言设计者们意识到并为我们提供了更好的工具,用来生成不同运行环境下的机器代码。c#为我们添加了Conditional特性用于指示某个方法的调用是基于环境设置的。这种方法比起#if/#endif块它对条件编译阐述的更清晰。编译器认识Conditional特性,所以当Conditional特性应用时它能更好的检验代码。Conditional特性应用于方法级,所以它迫使你把所有条件型代码一一写到相应的方法中。当你要创建条件代码块时请使用Conditional特性而不是#if/#endif块。
    许多老练的程序员在他们的代码中用条件编译来检查先决条件(pre-conditions)和后续条件(post-conditions)。你可能会写一个私有方法来检查所有的类和对象变量。这个方法是有条件的编译的从而它只会出现在你的debug版本。
private void CheckState( )
{
// The Old way:
#if DEBUG
  Trace.WriteLine( 
"Entering CheckState for Person" );

  
// Grab the name of the calling routine:
  string methodName =
    
new StackTrace( ).GetFrame( 1 ).GetMethod( ).Name;

  Debug.Assert( _lastName 
!= null,
    methodName,
    
"Last Name cannot be null" );

  Debug.Assert( _lastName.Length 
> 0,
    methodName,
    
"Last Name cannot be blank" );

  Debug.Assert( _firstName 
!= null,
    methodName,
    
"First Name cannot be null" );

  Debug.Assert( _firstName.Length 
> 0,
    methodName,
    
"First Name cannot be blank" );

  Trace.WriteLine( 
"Exiting CheckState for Person" );
#endif
}
    使用#if/#endif编译选项(pragmas),你已经为你的release版本创建了一个空方法。CheckState()方法在所有的版本中都被调用,但它在release版本中不做任何事情,但与此同时在JIT层面它得付出一点小小的代价,如装载、方法调用。
    上面的代码是可以正常工作的,但可能会导致一些微妙的bug产生而且只出现在release版本。下面的代码揭示了在使用#if/#endif条件编译块后可能会发生的常见问题:
public void Func( )
{
  
string msg = null;

#if DEBUG
  msg 
= GetDiagnostics( );
#endif
  Console.WriteLine( msg );
}
    上面的代码在debug版本工作正常,但是release版本输出一空行。你的release版本很乐意输出一空行信息。那不是你的意图。傻眼了吧,但编译器不能帮助你。你确实把你的基础逻辑写在条件块中。在你的代码中一些零碎的#if/#endif条件编译块会导致你难于诊断不同版本的不同行为。
    c#有一个更好的选择:Conditional特性。利用它我们可以将那些在特定环境变量被定义时或被设定为某个值时才发挥功效的函数孤立出来。这个特性的一般用法是对你的代码使用DEBUG条件,而.NET框架类库已经为这个用法准备了基础功能。下例展示了如何运用Debug工具类以及Conditional特性是如何工作的、何时在你的代码中添加它:
private void CheckState( )
{
  
// Grab the name of the calling routine:
  string methodName =
    
new StackTrace( ).GetFrame( 1 ).GetMethod( ).Name;

  Trace.WriteLine( 
"Entering CheckState for Person:" );
  Trace.Write( 
"\tcalled by " );
  Trace.WriteLine( methodName );

  Debug.Assert( _lastName 
!= null,
    methodName,
    
"Last Name cannot be null" );

  Debug.Assert( _lastName.Length 
> 0,
    methodName,
    
"Last Name cannot be blank" );

  Debug.Assert( _firstName 
!= null,
    methodName,
    
"First Name cannot be null" );

  Debug.Assert( _firstName.Length 
> 0,
    methodName,
    
"First Name cannot be blank" );

  Trace.WriteLine( 
"Exiting CheckState for Person" );
}
    在这个方法里你可能只碰到了一小部分方法,那么让我们简略的看一下吧。StackTrace类利用反射来获得调用的方法的名字(参见Item43)。虽然代价昂贵,但不失为一种很好的简化工作的途径,比如生成程序流程的信息,这里利用它得到了方法的名字。剩下的方法可能来自System.Diagnostics.Debug类或System.Diagnostics.Trace类。方法Debug.Assert检查条件,如果条件失败程序则结束。余下的参数用来输出当条件失败时要显示的信息的定义。Trace.WriteLine向debug控制台写诊断信息。所以这个方法被用来当person对象非法时输出相关信息并终止程序。你可以在你所有的方法和属性中调用这个方法作为先决和后续条件:
public string LastName
{
  
get
  {
    CheckState( );
    
return _lastName;
  }
  
set
  {
    CheckState( );
    _lastName 
= value;
    CheckState( );
  }
}
    如果有人对LastName设置空值或null,CheckState()第一时间触发断言,然后你就可以锁定set访问器检查其参数并修正它。这就是你想要的。
    但是这样做每次都会花费额外的工作,你可能只想在debug版本中才这样做,这时候Conditional特性出现:
[ Conditional( "DEBUG" ) ]
private void CheckState( )
{
  
// same code as above
}
    Conditional特性告诉c#编译器只有当编译器侦测到DEBUG环境变量时才对方法进行调用。另外,Conditional特性不会影响CheckState()方法产生的代码,只是修改其调用机制。如果DEBUG被定义,你获得:
public string LastName
{
  
get
  {
    CheckState( );
    
return _lastName;
  }
  
set
  {
    CheckState( );
    _lastName 
= value;
    CheckState( );
  }
}
    如果没有定义,则得到:
public string LastName
{
  
get
  {
    
return _lastName;
  }
  
set
  {
    _lastName 
= value;
  }
}
    无论环境变量是什么状态,CheckState()方法体仍是一样的。这只是一个例子告诉你.NET里为什么需要理解编译时和JIT之间的区别。无论是DEBUG环境变量定义与否,CheckState()方法被编译并传送至程序集中。这看起来有点低效,但只占用了点硬盘空间而矣。CheckState()方法不会被装载至内存及真正编译(JITed)除非它被调用。它非实质地存在于程序集文件中。这种策略增加了灵活性并只需要一点点开销。如果你想更深入的了解你可以参看.NET框架类库的Debug类,在任何一台装有.NET框架的机器上,System.dll包含了Debug类的所有方法的代码。环境变量控制其是否应该被调用。
    你也可以创建依赖一个或多个环境变量的方法。当你用多个Conditional特性时,它们用OR联合起来。举个例子,CheckState可能想在DEBUG或TRACE为true时调用:
[ Conditional( "DEBUG" ),
  Conditional( 
"TRACE" ) ]
private void CheckState( )
    如果你想得到一个AND式的工作方式,你必须在你的代码中使用预处理命令定义标记:
#if ( VAR1 && VAR2 )
#define BOTH
#endif
    的确如果你想创建依赖多于一个环境变量的条件例程(routine)时,你得使用原先的老办法#if。#if为我们产生一个新的标记,但避免在编译选项内添加任何可运行的代码。
    Conditional特性只能应用于整个方法,另外方法也只能返回void值。你不能在方法内部及返回其他值的地方使用Conditional特性。取而代之的是,你应该仔细创建一个条件方法,并孤立其条件行为。你仍需要留意这些条件属性是否对对象状态有副作用,在这点上Conditional特性比#if/#endif代码块做的好。用#if/#endif块你可能会错误的移除一些重要的方法或签名。
    前面的例子用了我们预定义的标识符DEBUG和TRACE,同时你也可以扩展成任何符号。Conditional特性可以由标记通过多种途径进行控制。你可以在编译命令行上定义,也可以在系统环境变量里定义,或者从源代码的编译选项里定义。
    使用Conditional属性可以比使用#if/#endif生成更高效的IL代码。在专门针对函数时,它更有优势,它会强制你在条件代码上使用更好的结构。编译器使用Conditional属性来帮助你避免因使用#if/#endif而产生的常见的错误。条件属性比起预处理,它为你区分条件代码提供了更好的支持。