Effective C# 4:use conditional attributes instead of #if

来源:互联网 发布:水乳套装推荐知乎 编辑:程序博客网 时间:2024/06/05 16:14

Item 4 use conditional attributes insteadof #if

使用Conditional特性替代#if

#if/#endif blocks have been used to producedifferent builds from the same source, most often debug and release variants.But these have never been a tool we were happy to use. #if/#endif blocks are too easily abused,creating code that is hard to understand and harder to debug. Languagedesigners have responded by creating better tools to produce different machinecode for different environments. C# has added the Conditional attribute to indicate whether amethod should be called based on an environment setting. It's a cleaner way todescribe conditional compilation than #if/#endif. The compiler understands the Conditional attribute, so it can do a betterjob of verifying code when conditional attributes are applied. The conditionalattribute is applied at the method level, so it forces you to separateconditional code into distinct methods. Use the Conditional attribute instead of #if/#endif blocks when you createconditional code blocks.

#if#endif块早已被用来从同样的源代码中编译出不同的产物,大多数是debugrelease之间的变化。但是它们从来不是我们喜欢使用的一个工具。#if#endif太容易被滥用了,生成的代码太难理解也较难调试。语言的设计者已经对此做出了响应:创造更好的工具来为不同的环境生成不同的机器代码。C#已经加入了Conditional特性来标示一个方法是否应该在某种环境设置下被调用。对Conditional特性编译进行描述比#if#endif块更清晰。编译器知道Conditional特性,因此它可以更好的在Conditional特性被应用的情况下验证代码。Conditional特性是应用在方法级别的,因此,它强迫你将使用条件的代码拆分成不同的方法。当你创建使用条件的代码块时,使用Conditional特性替代#if

Most veteran programmers have used conditional compilation to checkpre- and post-conditions in an object. You would write a private method tocheck all the class and object invariants. That method would be conditionallycompiled so that it appeared only in your debug builds.

绝大多数程序员老手都使用过条件编译在对象内来检查前置条件和后置条件。你可能写了一个private的方法来检查所有的类和对象变量。这个方法应该是条件编译的,那样才能仅仅出现在debug版本中。

  1. private void CheckState()
  2.     {
  3.      //the old way
  4. #if DEBUG
  5.      Trace.WriteLine("Entering CheckState for Person");
  6.      //grab the name of the calling routine
  7.      String methodName = new StackTrace().GetFrame(1).GetMethod().Name;
  8.      Debug.Assert(lastName != null, methodName, "Last Name cannot be null");
  9.      Debug.Assert(lastName.Length>0, methodName, "Last Name cannot be blank");
  10.      Debug.Assert(firstName != null, methodName, "first Name cannot be null");
  11.      Debug.Assert(firstName.Length >0, methodName, "first Name cannot be blank");
  12.      Trace.WriteLine("Exiting CheckState for Person");
  13. #endif
  14. }

Using the #if and #endif pragmas, you've created an emptymethod in your release builds. The CheckState() method gets called in all builds,release and debug. It doesn't do anything in the release builds, but you payfor the method call. You also pay a small cost to load and JIT the emptyroutine.

使用#if#endif,在release版本中就产生了一个空方法。CheckState()方法在所有的编译版本中被调用,不管是release版本还是debug版本。在release版本中它不做任何事情,但是你得为方法调用付出代价,同时,还得为加载和JIT编译这个空子程序花费一点代价。

This practice works fine, but can lead to subtle bugs that appearonly in release builds. The following common mistake shows what can happen whenyou use pragmas for conditional compilation.

这个做法可以工作得很好,但是在release版本中可能会导致细微的bug。下面的常见的错误,告诉你使用#if#endif条件编译时可能发生什么事情。

 

  1.    public void Func()
  2.     {
  3.        String msg = null;
  4. #if DEBUG
  5.        msg = GetDiagnostics();
  6. #endif
  7.        Console.WriteLine(msg);
  8.    }

Everything works fine in your debug build, but the release buildsprint a blank line. Your release builds happily print a blank message. That'snot your intent. You goofed, but the compiler couldn't help you. You have codethat is fundamental to your logic inside a conditional block. Sprinkling yoursource code with #if/#endif blocks makes it hard to diagnose the differences in behavior withthe different builds.

debug版本里面一切都工作的很好,但是在release版本里面会打印出一个空白行。你的release版本会很乐意这样做,但那却不是你的本意。你自己搞砸了,编译器什么也帮不了。你刚刚写得那些代码建立在条件块里面的逻辑之上。将源代码散播在#if#endif块之间,使得诊断不同版本之间行为的不同变得困难。

C# has a better alternative: the Conditional attribute. Using the Conditional attribute, you can isolatefunctions that should be part of your classes only when a particularenvironment variable is defined or set to a certain value. The most common useof this feature is to instrument your code with debugging statements. The .NETFramework library already has the basic functionality you need for this use.This example shows how to use the debugging capabilities in the .NET FrameworkLibrary, to show you how Conditional attributes work and when to add them to yourcode.

C#有更好的替代方法:Conditional特性。使用Conditional特性,可以将下列方法隔离起来:只有在某个特殊的环境变量被定义或者设置给某个特定值的时候,这个方法才是你的类的一部分。Conditional特性最常用的地方就是将代码变成调试语句。.Net框架类库已经有了这种用法的基本功能了。这个例子展示了如何在.Net框架类库中使用调试能力,展示了Conditional如何使用,展示了该在什么时候添加到代码中。

When you build the Person object, you add a method to verifythe object invariants:

当你创建Person对象的时候,添加一个方法来验证对象的变量。

  1. private void CheckState()
  2. {
  3.     //grab the name of the calling routine
  4.     String methodName = new StackTrace().GetFrame(1).GetMethod().Name;
  5.  
  6.     Trace.WriteLine("Entering CheckState for Person");
  7.     Trace.Write("/tcalled by");
  8.     Trace.WriteLine(methodName);
  9.  
  10.     Debug.Assert(lastName != null, methodName, "Last Name cannot be null");
  11.     Debug.Assert(lastName.Length>0, methodName, "Last Name cannot be blank");
  12.     Debug.Assert(firstName != null, methodName, "first Name cannot be null");
  13.     Debug.Assert(firstName.Length >0, methodName, "first Name cannot be blank");
  14.  
  15.     Trace.WriteLine("Exiting CheckState for Person");
  16. }

You might not have encounteredmany library functions in this method, so let's go over them briefly. The StackTrace class gets the name of thecalling method using Reflection (see Item 43). It's rather expensive, but itgreatly simplifies tasks, such as generating information about program flow.Here, it determines the name of the method that called CheckState. The remaining methods are partof the System.Diagnostics.Debug class or the System.Diagnostics.Trace class. The Debug.Assert method tests a condition andstops the program if that condition is false. The remaining parameters definemessages that will be printed if the condition is false. Trace.WriteLine writes diagnostic messages to thedebug console. So, this method writes messages and stops the program if aperson object is invalid. You would call this method in all your public methodsand properties as a precondition and a post-condition:

你很可能还没有接触过这个方法里面的很多库方法,那我们概要的浏览一下吧。StackTrace类使用反射获得正在调用的方法的名字(Item43)。这比较耗费资源,但是却让工作变得更简单,例如生成程序流信息。这里,决定了调用CheckState的方法名。剩下的方法都是System.Diagnostics.Debug或者System.Diagnostics.Trace的一部分。Debug.Assert方法测试一个条件,如果这个条件为false的话,就会终止程序,在条件为false的情况下,剩下的参数定义了会被打印出来的信息。Trace.WriteLine向调试终端输出诊断信息。因此,如果一个person对象无效的情况下,这个方法将输出信息并将终止程序。你可以在所有的public方法和属性中调用这个方法,将其作为前置或者后置条件。

  1. public string LastName
  2. {
  3.     get
  4.     {
  5.         CheckState();
  6.         return lastName;
  7.     }
  8.     set
  9.     {
  10.         CheckState();
  11.         lastName = value;
  12.         CheckState();
  13.     }
  14. }

CheckState fires an assert the first timesomeone tries to set the last name to the empty string, or null. Then you fixyour set accessor to check the parameter used for LastName. It's doing just what you want.

当第一次有人尝试将lastName赋值为空字符串或者null时,CheckState就会发出assert错误,然后你就可以让set访问符检查给LastName使用的参数。这正是你想要的。

But this extra checking in each public routine takes time. You'llwant to include this extra checking only when creating debug builds. That'swhere the Conditional attribute comes in:

但是在每个公共子程序中的额外检查是要花费时间的。你希望仅仅当创建debug版本的程序时才做这些额外的检查。这,正是Conditional特性要出现的地方:

  1. [Conditional("DEBUG")]
  2. public void CheckState()
  3. {
  4.     //same code as above
  5. }

The Conditional attribute tells the C# compiler that this methodshould be called only when the compiler detects the DEBUG environment variable.The Conditional attribute does not affect the code generated for the CheckState()function; it modifies the calls to the function. If the DEBUG symbol isdefined, you get this:

Conditional特性告诉C#编译器,只有当它检测到DEBUG环境变量时,这个方法才应该被调用。Conditional特性不影响CheckState()方法生成的代码;它修改了对这个方法的调用。如果定义了DEBUG符号,你会得到:

  1. public string LastName
  2. {
  3.     get
  4.     {
  5.         CheckState();
  6.         return lastName;
  7.     }
  8.     set
  9.     {
  10.         CheckState();
  11.         lastName = value;
  12.         CheckState();
  13.     }
  14. }

If not, you get this:

如果没有定义DEBUG符号,你会得到:

  1. public string LastName
  2. {
  3.     get
  4.     {
  5.         return lastName;
  6.     }
  7.     set
  8.     {
  9.         lastName = value;
  10.     }
  11. }

The body of the CheckState() function is the same, regardless of thestate of the environment variable. This is one example of why you need tounderstand the distinction made between the compilation and JIT steps in .NET.Whether the DEBUG environment variable is defined or not, the CheckState()method is compiled and delivered with the assembly. That might seeminefficient, but the only cost is disk space. The CheckState() function doesnot get loaded into memory and JITed unless it is called. Its presence in theassembly file is immaterial. This strategy increases flexibility and does sowith minimal performance costs. You can get a deeper understanding by lookingat the Debug class in the .NET Framework. On any machine with the .NETFramework installed, the System.dll assembly does have all the code for all themethods in the Debug class. Environment variables control whether they getcalled when callers are compiled.

CheckState()方法的方法体是一样的,并不考虑环境变量的状态。你需要理解C#编译过程和JIT编译步骤的区别,为什么呢,这就是一个例子。不管DEBUG环境变量是否被定义,CheckState()方法都会被编译并且随着程序集发布。这样做可能不是很有效率,但是唯一的代价就是硬盘空间。除非被调用,CheckState()方法并不会被加载到内存中,更不会被JIT编译。将它编译到程序集文件中的影响是非常小的,这种策略通过最小的性能损失增加了灵活性。通过参看.Net框架中的Debug类,你会理解的更深入。在任何安装了.Net框架的机器上,System.dll程序集拥有Debug类的所有方法的所有代码。环境变量控制着当调用者被编译时,它们是否被调用。

You can also create methods thatdepend on more than one environment variable. When you apply multipleconditional attributes, they are combined with OR.For example, this version of CheckStatewould be called when either DEBUGor TRACE is TRue:

你也可以创建基于一个或者多个环境变量的方法。当使用多Conditional特性时,它们之间是以OR的关系被组合的。例如,下面这个版本的CheckState()就会在DEBUG或者TRACE任何一个是true的情况下,被调用:

  1.         [Conditional("DEBUG"),Conditional("TRACE")]
  2.      public void CheckState()

To create a construct using AND, you need to definethe preprocessor symbol yourself using preprocessor directives in your sourcecode:

如果要创建一个使用And的,就需要你自己在源代码中定义预处理符号了:

  1. #if(VAR1 && VAR2)
  2. #define BOTH
  3. #endif

Yes, to create a conditional routine that relies on the presence ofmore than one environment variable, you must fall back on your old practice of #if.All #if does is create a new symbol for you. But avoid putting any executablecode inside that pragma.

是的,创建基于不止一个环境变量的条件程序,你必须返回到旧有的#if实践中去。所有的#if会给你创建一个新的符号。但是一定要注意避免在#if#endif里面加入任何代码。

The Conditional attribute can be applied only to entire methods. Inaddition, any method with a Conditional attribute must have a return type of void.You cannot use the Conditional attribute for blocks of code inside methods orwith methods that return values. Instead, create carefully constructedconditional methods and isolate the conditional behavior to those functions.You still need to review those conditional methods for side effects to theobject state, but the Conditional attribute localizes those points much betterthan #if/#endif. With #if and #endif blocks, you can mistakenly removeimportant method calls or assignments.

Conditional特性仅仅能够被应用于整个方法。另外,任何带有Conditional特性的方法必须返回void。不能在任何方法的内部代码块上使用Conditional特性,也不能在有返回值的方法上使用Conditional特性。相反,为了使用Conditional特性,需要将条件性的行为隔离到单独的方法中。你仍然需要考虑Conditional特性对对象状态的其他影响,但是Conditional特性使得这些方面比#if #endif好多了。使用#if #endif的话,你会错误的移除重要的方法调用或者赋值。

The previous examples use the predefined DEBUG or TRACE symbols. Butyou can extend this technique for any symbols you define. The Conditionalattribute can be controlled by symbols defined in a variety of ways. You candefine symbols from the compiler command line, from environment variables inthe operating system shell, or from pragmas in the source code.

前面的例子使用了预定义的DEBUGTRACE符号。但是你可以为任何自定义的符号使用该技术。Conditional特性可以被以一系列方式定义的符号所控制。可以通过编译器、命令行、操作系统外壳的环境变量、源代码的pragmas来定义符号。

The Conditional attribute generates more efficient IL than #if/#endifdoes. It also has the advantage of being applicable only at the function level,which forces you to better structure your conditional code. The compiler usesthe Conditional attribute to help you avoid the common errors we've all made byplacing the #if or #endif in the wrong spot. The Conditional attribute providesbetter support for you to cleanly separate conditional code than thepreprocessor did.

Conditional特性生成比#if#endif高效得多的IL。它只能够被应用在方法的级别上,这也是一个优势,可以使你更好的规划条件代码。编译器使用Conditional特性来帮助你避免我们已经做过的将#if#endif放在错误的地方而产生的错误。为了你能更清晰的分隔条件代码,Conditional特性比预处理提供了更好的支持。

原创粉丝点击