一、C#预处理器指示符(详细点击连接到MSDN查看)
#if
#else
#elif
#endif
# define
#undef
#warning
#error
#line
#region
#endregion
#pragma
#pragma warning
#pragma checksum
二、详细介绍
下面将对这些指令逐一介绍(转录自http://www.cnblogs.com/aivdesign/articles/1241775.html)
1. #define和#undef
#define可以定义符号。当将符号用作传递给#if指令的表达式时,此表达式的计算结果true。如
#define DEBUG
它告诉编译器存在给定名称的符号,在本例中是DEBUG。这个符号不是实际代码的一部分,只在编译代码时存在。
#undef正好相反,它删除一个符号。
必须把#define和#undef命令放在C#源码的开头,即在要编译的任何代码之前。它不像C++中那样可以定义常数值。
#define本身并无大用,它需要配合#if指令使用。
如
#define DEBUG
int DoSw(double x)
{
#if DEBUG
COnsole.WriteLine("x is"+X);
#edif
}
2. #if, #elif, #else和#endif
这些指令告诉编译器是否要编译某个代码块。看下面的方法:
static void PrintVersion()
{
#if V3
Console.WriteLine("Version 3.0");
#elif V2
Console.WriteLine("Version 2.0");
#else
Console.WriteLine("Version 1.0");
#endif
}
上面的代码会根据定义的符号来打印不同的版本信息。 这种方式成为条件编译。
注意:
使用#if不是条件编译代码的唯一方式,C#还提供了通过Conditional属性的机制。
#if和#elif还支持一组逻辑运算符!=,==,!=和|| 。如果符号存在,符号的值被认为是true,否则为false,如:
#if V3 || (V2 == true) // if 定义了V3或V2符号...
3. #warning和#error
这也是两个很有用的预处理器指令,编译器遇到它们时,会分别产生警告或错误信息。如果遇到#warning指令,会向用户显示#warning指令后面的文本。实际上,在VS2005中,IDE会直接将信息标识出来:
而如果编译器遇到#error,就会立即退出编译,不会产生IL代码。
使用这两个指令可以检查#define是不示做错了什么事,使用#warning可以让自己想起做什么事。
#if DEBUG && RELEASE
#error "You 've define xxxx!"
#ednif
#warning "Don't forgot to removexxxxxx!"
Console.WriteLine("I have this job");
4. #region和#endregion
#region和#endregion指令用于把一段代码标记为有指定名称的一个块,如下所示:
#region private methods
private int x;
private int y;
#endregion
这两个指令不会影响编译过程。但可以为VS编辑器所识别,从而使得代码显示布局更为清晰。
5. #line
可以用于改变编译器在警告和错误信息中显示的文件的名字和行号信息。如果编写代码时,在把代码发给比编译器前,要使用某些软件包改变键入的代码,可以使用这个指令,因为这意味着比编译器的报告的行号或文件名与文件中的行号或编译的文件名不匹配。#line指令可以用于恢复这种匹配也可以使用语法#line default 把行号恢复默认。
#line 164 "cORE.CS"
#LINE default
6. #pragma,#pragma warning,#pragma checksum
#pragma:为编译器提供特殊的指令,说明如何编译包含杂注的文件。
#pragma warning:可启用或禁用某些警告。
#pragma checksum:生成源文件的校验和,以帮助调试ASP.NET页。
可以抑制或恢复指定的编译警告。与命令行选项不同,#pragma指令可以在类和方法上执行,对抑制什么警告和抑制的时间进行更精细的控制。
#pragma warning disable 169
public class Aclass
{
int nFiled;
}
#pragma warning restore 169
三、更好的替代方案
利用条件属性代替(转录自http://www.cnblogs.com/wucountry/archive/2007/02/13/649549.html)
使用#if/#endif 块可以在同样源码上生成不同的编译(结果),大多数debug和release两个版本。但它们决不是我们喜欢用的工具。由于#if/#endif很容易被滥用,使得编写的代码难于理解且更难于调试。程序语言设计者有责任提供更好的工具,用于生成在不同运行环境下的机器代码。C#就提供了条件属性(Conditional attribute)来识别哪些方法可以根据环境设置来判断是否应该被调用。
这个方法比条件编译#if/#endif更加清晰明白。编译器可以识别Conditional属性,所以当条件属性被应用时,编译器可以很出色的完成工作。条件属性是在方法上使用的,所以这就使用你必须把不同条件下使用的代码要写到不同的方法里去。当你要为不同的条件生成不同的代码时,请使用条件属性而不是#if/#endif块。
很多编程老手都在他们的项目里用条件编译来检测先决条件(per-conditions)和后续条件(post-conditions)。
(注:per-conditions,先决条件,是指必须满足的条件,才能完成某项工作,而post-conditions,后续条件,是指完成某项工作后一定会达到的条件。例如某个函数,把某个对象进行转化,它要求该对象不能为空,转化后,该对象一定为整形,那么:per-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()方法会在所有的版本(debug和release)中调用。而在release中它什么也不做,但它要被调用。因此你还是得为例行公事的调用它而付出小部份代价。
不管怎样,上面的实践是可以正确工作的,但会导致一个只会出现在release中的细小BUG。下面的就是一个常见的错误,它会告诉你用条件编译时会发生什么:
public void Func( )
{
string msg = null;
#if DEBUG
msg = GetDiagnostics( );
#endif
Console.WriteLine( msg );
}
这一切在Debug模式下工作的很正常,但在release下却输出的为空行。release模式很乐意给你输出一个空行,然而这并不是你所期望的。傻眼了吧,但编译器帮不了你什么。你的条件编译块里的基础代码确实是这样逻辑。一些零散的#if/#endif块使你的代码在不同的编译条件下很难得诊断(diagnose)。
C#有更好的选择:这就是条件属性。用条件属性,你可以在指定的编译环境下废弃一个类的部份函数, 而这个环境可是某个变量是否被定义,或者是某个变量具有明确的值。这一功能最常见的用法就是使你的代码具有调试时可用的声明。.Net框架库已经为你提供了了基本泛型功能。这个例子告诉你如何使用.net框架库里的兼容性的调试功能,也告诉你条件属性是如何工作的以及你在何时应该添加它:
当你建立了一个Person的对象时,你添加了一个方法来验证对象的不变数据(invariants):
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 类通过反射取得了调用方法的的名字。这样的代价是昂贵的,但它确实很好的简化了工作,例如生成程序流程的信息。这里,断定了CheckState所调用的方法的名字。被判定(determining)的方法是System.Diagnostics.Debug类的一部份,或者是System.Diagnostics.Trace类的一部份。Degbug.Assert方法用来测试条件是否满足,并在条件为false时会终止应用程序。剩下的参数定义了在断言失败后要打印的消息。Trace.WriteLine输出诊断消息到调试控制台。因此,这个方法会在Person对象不合法时输出消息到调试控制台,并终止应用程序。你可以把它做为一个先决条件或者后继条件,在所有的公共方法或者属性上调用这个方法。
public string LastName
{
get
{
CheckState( );
return _lastName;
}
set
{
CheckState( );
_lastName = value;
CheckState( );
}
}
在某人试图给LastName赋空值或者null时,CheckState会在第一时间引发一个断言。然后你就可以修正你的属性设置器,来为LastName的参数做验证。这就是你想要的。
但这样的额外检测存在于每次的例行任务里。你希望只在调试版中才做额外的验证。这时候条件属性就应运而生了:
[ Conditional( "DEBUG" ) ]
private void CheckState( )
{
// same code as above
}
Conditional属性会告诉C#编译器,这个方法只在编译环境变量DEBUG有定义时才被调用。同时,Conditional属性不会影响CheckState()函数生成的代码,只是修改对函数的调用。如果DEBGU标记被定义,你可以得到这:
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(译注:这里的JITed是指真正的编译为机器代码),除非它被调用。它存在于程序集文件里并不是本质问题。这样的策略是增强(程序的)可伸缩性的,并且这样只是一点微不足道的性能开销。你可以通过查看.Net框架库中Debug类而得到更深入的理解。在任何一台安装了.Net框架库的机器上,System.dll程序集包含了Debug类的所有方法的代码。由环境变量在编译时来决定是否让由调用者来调用它们。
你同样可以写一个方法,让它依懒于不只一个环境变量。当你应用多个环境变量来控制条件属性时,他们时以or的形式并列的。例如,下面这个版本的CheckState会在DEBUG或者TRACE为真时被调用:
[ Conditional( "DEBUG" ),
Conditional( "TRACE" ) ]
private void CheckState( )
如果要产生一个and的并列条件属性,你就要自己事先直接在代码里使用预处理命令定义一个标记:
#if ( VAR1 && VAR2 )
#define BOTH
#endif
是的,为了创建一个依懒于前面多个环境变量的条件例程(conditional routine),你不得不退到开始时使用的#if实践中了。#if为我们产生一个新的标记,但避免在编译选项内添加任何可运行的代码。
Conditional属性只能用在方法的实体上,另外,必须是一个返回类型为void的方法。你不能在方法内的某个代码块上使用Conditional,也不能在一个有返回值的方法上使用Conditional属性。取而代之的是,你要细心构建一个条件方法,并在那些方法上废弃条件属性行为。你仍然要回顾一下那些具有条件属性的方法,看它是否对对象的状态具有副作用。但Conditional属性在安置这些问题上比#if/#endif要好得多。在使用#if/#endif块时,你很可能错误的移除了一个重要的方法调用或者一些配置。
前面的例子合用预先定义的DEBUG或者TRACE标记,但你可以用这个技巧,扩展到任何你想要的符号上。Conditional属性可以由定义标记来灵活的控制。你可以在编译命令行上定义,也可以在系统环境变量里定义,或者从源代码的编译选择里定义。
使用Conditional属性可以比使用#if/#endif生成更高效的IL代码。在专门针对函数时,它更有优势,它会强制你在条件代码上使用更好的结构。编译器使用Conditional属性来帮助你避免因使用#if/#endif而产生的常见的错误。条件属性比起预处理,它为你区分条件代码提供了更好的支持。