1.6 Selectively Include Code at Build Time


Problem

You need to selectively include and exclude sections of source code from your compiled assembly.

Solution

Use the #if , #elif , #else , and #endif preprocessor directives to identify blocks of code that should be conditionally included in your compiled assembly. Use the System.Diagnostics.ConditionalAttribute attribute to define methods that should only be called conditionally. Control the inclusion of the conditional code using the #define and #undef directives in your code, or use the /define switch when you run the C# compiler.

Discussion

If you need your application to function differently depending on factors such as the platform or environment on which it runs, you can build run-time checks into the logic of your code that trigger the variations in operation. However, such an approach can bloat your code and affect performance, especially if there are many variations to support or many locations where evaluations need to be made. An alternative approach is to build multiple versions of your application to support the different target platforms and environments. Although this approach overcomes the problems of code bloat and performance degradation, it would be an untenable solution if you had to maintain different source code for each version, so C# provides features that allow you to build customized versions of your application from a single code base.

The #if , #elif , #else , and #endif preprocessor directives allow you to identify blocks of code that the compiler should include in your assembly only if specified symbols are defined at compile time. Symbols function as on/off switches; they don't have values ”either the symbol is defined, or it is not. To define a symbol you can use either the #define directive in your code or use the /define compiler switch. Symbols defined using #define are active until the end of the file in which they are defined. Symbols defined using the /define compiler switch are active in all source files that are being compiled. To undefine a symbol defined using the /define compiler switch, C# provides the #undef directive, which is useful if you want to ensure a symbol is not defined in specific source files. All #define and #undef directives must appear at the top of your source file before any code, including any using directives. Symbols are case sensitive.

In this example, the code assigns a different value to the local variable platformName based on whether the winXP , win2000 , winNT , or Win98 symbols are defined. The head of the code defines the symbols win2000 and release (not used in this example) and undefines the win98 symbol in case it was defined on the compiler command line.

 #define win2000 #define release #undef  win98 using System; public class ConditionalExample {     public static void Main() {              // Declare a string to contain the platform name         string platformName;                  #if winXP       // Compiling for Windows XP             platformName = "Microsoft Windows XP";         #elif win2000   // Compiling for Windows 2000             platformName = "Microsoft Windows 2000";         #elif winNT     // Compiling for Windows NT             platformName = "Microsoft Windows NT";         #elif win98     // Compiling for Windows 98             platformName = "Microsoft Windows 98";         #else           // Unknown platform specified             platformName = "Unknown";         #endif                  Console.WriteLine(platformName);     } } 

To build the ConditionalExample class (contained in a file named ConditionalExample.cs) and define the symbols winXP and DEBUG (not used in this example), use the command csc /define:winXP;DEBUG ConditionalExample.cs .

The #if .. #endif construct evaluates #if and #elif clauses only until it finds one that evaluates to true, meaning that if you define multiple symbols ( winXP and win2000 , for example), the order of your clauses is important. The compiler includes only the code in the clause that evaluates to true. If no clause evaluates to true, the compiler includes the code in the #else clause.

You can also use logical operators to base conditional compilation on more than one symbol. Table 1.1 summarizes the supported operators.

Table 1.1: Logical Operators Supported by the #if..#endi f Directive

Operator

Example

Description

==

#if winXP == true

Equality. Evaluates to true if the symbol winXP is defined. Equivalent to #if winXP .

!=

#if winXP != true

Inequality. Evaluates to true if the symbol winXP is not defined. Equivalent to #if !winXP .

&&

#if winXP && release

Logical AND. Evaluates to true only if the symbols winXP AND release are defined.

#if winXP release

Logical OR. Evaluates to true if either of the symbols winXP OR release are defined.

()

#if ( winXP win2000 ) && release

Parentheses allow you to group expressions. Evaluates to true if the symbols winXP OR win2000 are defined AND the symbol release is defined.

Warning  

You must be careful not to overuse conditional compilation directives and not to make your conditional expressions too complex; otherwise , your code can quickly become confusing and unmanageable ”especially as your projects become larger.

A less flexible but more elegant alternative to the #if preprocessor directive is the attribute System.Diagnostics.ConditionalAttribute . If you apply ConditionalAttribute to a method, the compiler will ignore any calls to the method if the symbol specified by ConditionalAttribute is not defined at the calling point. In the following code, ConditionalAttribute specifies that calls to the DumpState method should be included in a compiled assembly only if the symbol DEBUG is defined during compilation.

 [System.Diagnostics.Conditional("DEBUG")] public static void DumpState() {//} 

Use of ConditionalAttribute centralizes your conditional compilation logic on the method declaration and means that you can freely include calls to conditional methods without the need to litter your code with #if directives. However, because the compiler literally removes calls to the conditional method from your code, your code can't have dependencies on return values from the conditional method. This means that you can apply ConditionalAttribute only to methods that return void .

You can apply multiple ConditionalAttribute instances to a method in order to produce logical OR behavior. Calls to the following version of the DumpState method will be compiled only if the DEBUG OR TEST symbols are defined.

 [System.Diagnostics.Conditional("DEBUG")] [System.Diagnostics.Conditional("TEST")] public static void DumpState() {//} 

Achieving logical AND behavior is not as clean and involves the use of an intermediate conditional method, quickly leading to overly complex code that is hard to understand and maintain. Here is a quick example that requires definition of both the DEBUG AND TEST symbols for the DumpState functionality (contained in DumpState2 ) to be called.

 [System.Diagnostics.Conditional("DEBUG")] public static void DumpState() {     DumpState2(); } [System.Diagnostics.Conditional("TEST")] public static void DumpState2() {//} 
Note  

The Debug and Trace classes from the System.Diagnostics namespace use ConditionalAttribute on many of their methods. The methods of the Debug class are conditional on the definition of the symbol DEBUG , and the methods of the Trace class are conditional on the definition of the symbol TRACE .




C# Programmer[ap]s Cookbook
C# Programmer[ap]s Cookbook
ISBN: 735619301
EAN: N/A
Year: 2006
Pages: 266

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net