The C Preprocessor Directives


The C# Preprocessor Directives

Besides the usual keywords, most of which you have now encountered, C# also includes a number of commands that are known as preprocessor directives. These commands never actually get translated to any commands in your executable code, but instead they affect aspects of the compilation process. For example, you can use preprocessor directives to prevent the compiler from compiling certain portions of your code. You might do this if you are planning to release two versions of the code — a basic version and an enterprise version that will have more features. You could use preprocessor directives to prevent the compiler from compiling code related to the additional features when you are compiling the basic version of the software. Another scenario is that you might have written bits of code that are intended to provide you with debugging information. You probably don't want those portions of code compiled when you actually ship the software.

The preprocessor directives are all distinguished by beginning with the # symbol.

Note

C++ developers will recognize the preprocessor directives as something that plays an important part in C and C++. However, there aren't as many preprocessor directives in C#, and they are not used as often. C# provides other mechanisms, such as custom attributes, that achieve some of the same effects as C++ directives. Also, note that C# doesn't actually have a separate preprocessor in the way that C++ does. The so- called preprocessor directives are actually handled by the compiler. Nevertheless, C# retains the name preprocessor directive because these commands give the impression of a preprocessor.

The next sections briefly cover the purposes of the preprocessor directives.

#define and #undef

#define is used like this:

 #define DEBUG 

What this does is tell the compiler that a symbol with the given name (in this case DEBUG) exists. It is a little bit like declaring a variable, except that this variable doesn't really have a value — it just exists. And this symbol isn't part of your actual code; it exists only for the benefit of the compiler, while the compiler is compiling the code, and has no meaning within the C# code itself.

#undef does the opposite, and removes the definition of a symbol:

 #undef DEBUG 

If the symbol doesn't exist in the first place, then #undef has no effect. Similarly, #define has no effect if a symbol already exists.

You need to place any #define and #undef directives at the beginning of the C# source file, before any code that declares any objects to be compiled.

#define isn't much use on its own, but when combined with other preprocessor directives, especially #if, it becomes very powerful.

Note

Incidentally, you might notice some changes from the usual C# syntax. Preprocessor directives are not terminated by semicolons, and normally constitute the only command on a line. That's because for the preprocessor directives, C# abandons its usual practice of requiring commands to be separated by semicolons. If it sees a preprocessor directive, it assumes the next command is on the next line.

#if, #elif, #else, and #endif

These directives inform the compiler whether or not to compile a block of code. Consider this method:

 int DoSomeWork(double x) { // do something #if DEBUG Console.WriteLine("x is " + x); #endif } 

This code will compile as normal, except for the Console.WriteLine() method call that is contained inside the #if clause. This line will only be executed if the symbol DEBUG has been defined by a previous #define directive. When the compiler finds the #if directive, it checks to see if the symbol concerned exists, and only compiles the code inside the #if clause if the symbol does exist. Otherwise, the compiler simply ignores all the code until it reaches the matching #endif directive. Typical practice is to define the symbol DEBUG while you are debugging, and have various bits of debugging-related code inside #if clauses. Then, when you are close to shipping, you simply comment out the #define directive, and all the debugging code miraculously disappears, the size of the executable file gets smaller, and your end users don't get confused by being shown debugging information. (Obviously, you would do more testing to make sure your code still works without DEBUG defined). This technique is very common in C and C++ programming and is known as conditional compilation.

The #elif (=else if) and #else directives can be used in #if blocks and have the intuitively obvious meanings. It is also possible to nest #if blocks:

 #define ENTERPRISE #define W2K // further on in the file #if ENTERPRISE // do something #if W2K // some code that is only relevant to enterprise // edition running on W2K #endif #elif PROFESSIONAL // do something else #else // code for the leaner version #endif 
Note

Note that, unlike the situation in C++, using #if is not the only way to compile code conditionally. C# provides an alternative mechanism through the Conditional attribute, which is explored in Chapter 11, "Reflection."

#if and #elif support a limited range of logical operators too, using the operators !, ==, !=, and ||. A symbol is considered to be true if it exists and false if it doesn't. For example:

 #if W2K && (ENTERPRISE==false) // if W2K is defined but ENTERPRISE isn't 

#warning and #error

Two other very useful preprocessor directives are #warning and #error. These will respectively cause a warning or an error to be raised when the compiler encounters them. If the compiler sees a #warning directive, it will display whatever text appears after the #warning to the user, after which compilation continues. If it encounters a #error directive, it will display the subsequent text to the user as if itwere a compilation error message and then immediately abandon the compilation, so no IL code willbe generated.

You can use these directives as checks that you haven't done anything silly with your #define statements; you can also use the #warning statements to remind yourself to do something:

 #if DEBUG && RELEASE #error "You've defined DEBUG and RELEASE  #endif #warning "Don't forget to remove this line before Console.WriteLine("*I hate this job*"); 

#region and #endregion

The #region and #endregion directives are used to indicate that a certain block of code is to be treated as a single block with a given name, like this:

 #region Member Field Declarations int x; double d; Currency balance; #endregion 

#line

The #line directive can be used to alter the file name and line number information that is output by the compiler in warnings and error messages. You probably won't want to use this directive that often. Its main use occurs if you are coding in conjunction with some other package that alters the code you are typing in before sending it to the compiler, since this will mean line numbers, or perhaps the file names reported by the compiler, won't match up to the line numbers in the files or the file names you are editing. The #line directive can be used to restore the match. You can also use the syntax #line default to restore the line to the default line numbering:

 #line 164 "Core.cs"   // we happen to know this is line 164 in the file // Core.cs, before the intermediate // package mangles it. // later on #line default      // restores default line numbering 

#pragma

The #pragma directive can either suppress or restore specific compiler warnings. Unlike command-line options, the #pragma directive can be implemented on a class or method level, allowing a fine-grain control of what warnings are suppressed and when. The following example disables the field not used warning and then restores it after the MyClass class compiles.

 #pragma warning disable 169 public class MyClass  { int neverUsedField; } #pragma warning restore 169 




Professional C# 2005
Pro Visual C++ 2005 for C# Developers
ISBN: 1590596080
EAN: 2147483647
Year: 2005
Pages: 351
Authors: Dean C. Wills

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