What Are Preprocessor Directives?
The #define and #undef Directives
The Conditional Compilation Constructs
The source code specifies the definition of a program. The preprocessor directives instruct the compiler how to treat the source code. For example, under certain conditions, you might want the compiler to ignore portions of the code, and under other conditions, you might want that code compiled. The preprocessor directives give you those options and several others.
In C and C++ there is an actual preprocessor phase, in which the preprocessor goes through the source code and prepares an output stream of text to be processed by the subsequent compilation phase. In C# there is no actual preprocessor. The “preprocessor” directives are handled by the compiler. The term, however, remains.
Some of the most important syntactic rules for preprocessor directives are the following:
#
character.
#
character.#
character and the directive.Here are some examples illustrating the rules:
The preprocessor directives are listed in Table 23-1.
Table 23-1. Preprocessor Directives
Directive | Summary of Meaning |
#define identifier |
Defines a compilation symbol |
#undef identifier |
Undefines a compilation symbol |
#if expression |
If the expression is true , compiles the following section |
#elif expression |
If the expression is true , compiles the following section |
#else |
If the previous #if or #elif expression is false , compiles the following section |
#endif |
Marks the end of an #if construct |
#region name |
Marks the beginning of a region of code; has no compilation effect |
#endregion name |
Marks the end of a region of code; has no compilation effect |
#warning message |
Displays a compile-time warning message |
#error message |
Displays a compile-time error message |
#line indicator |
Changes the line numbers displayed in compiler messages |
#pragma text |
Specifies information about the program context |
A compilation symbol is an identifier that has only two possible states. It is either defined or undefined. A compilation symbol has the following characteristics:
true
or false
. This includes C# keywords and identifiers declared in your C# code—both of which are fine.As shown in Table 23-1:
#define
directive declares a compilation symbol.#undef
directive undefines a compilation symbol.#define PremiumVersion
#define EconomyVersion
...
#undef PremiumVersion
The #define
and #undef
directives can be used only at the top of a source file, before any C# code is listed. After the C# code has started, the #define
and #undef
directives can no longer be used.
using System; // First line of C# code
#define PremiumVersion // Error
namespace Eagle
{
#define PremiumVersion // Error
...
The scope of a compilation symbol is limited to a single source file. Redefining a symbol that is already defined is perfectly fine—as long as it's before any C# code, of course.
#define AValue
#define BValue
#define AValue // Redefinition is fine.
Conditional compilation allows you to mark a section of source code to be either compiled or skipped, depending on whether a particular compilation symbol is defined.
There are four directives for specifying conditional compilation:
#if
#else
#elif
#endif
A condition is a simple expression that returns either true
or false
.
true
and false
can also be used in conditional expressions.Table 23-2. Conditions Used in the #if and #elif Directives
Parameter Type | Meaning | Evaluation |
Compilation symbol | Identifier, defined (or not) using the #define directive |
True : If the symbol has been defined using a #define directiveFalse : Otherwise |
Expression | Constructed using symbols and the operators ! , == , != , && , || |
True : If the expression evaluates to true False : Otherwise |
The following are examples of conditional compilation conditions:
The #if
and #endif
directives are the matching demarcations of a conditional compilation construct. Whenever there is an #if
directive, there must also be a matching #endif
.
Figure 23-1 illustrates the #if
and #if...#else
constructs.
#if
construct evaluates to true
, the code section following it is compiled. Otherwise, it is skipped.#if...#else
construct, if the condition evaluates to true
, CodeSection1 is compiled. Otherwise, CodeSection2 is compiled.Figure 23-1. The #if and #else constructs
For example, the following code illustrates a simple #if...#else
construct. If the symbol RightHanded
is defined, the code between the #if
and the #else
will be compiled. Otherwise, the code between the #else
and the #endif
will be compiled.
...
#if RightHanded
// Code implementing right-handed functionality
...
#else
// Code implementing left-handed functionality
...
#endif
Figure 23-2 illustrates the #if...#elif
and #if...#elif...#else
constructs.
#if...#elif
construct, if Cond1 evaluates to true
, CodeSection1 is compiled, and compilation continues after the #endif
.
true
, CodeSection2 is compiled, and compilation continues after the #endif
.true
or all the conditions have returned false
. If that's the case, none of the code sections in the construct are compiled, and compilation continues after the #endif
.#if...#elif...#else
construct works the same way, except that if no condition is true
, then the code section after the #else
is then compiled, and compilation continues after the #endif
.Figure 23-2. The #elif construct
The following code demonstrates the #if...#elif...#else
construct. The string containing the description of the version of the program is set to various values, depending on which compilation symbol is defined.
#define DemoVersionWithoutTimeLimit
...
const int intExpireLength = 30;
string strVersionDesc = null;
int intExpireCount = 0;
#if DemoVersionWithTimeLimit
intExpireCount = intExpireLength;
strVersionDesc = "This version of Supergame Plus will expire in 30 days";
#elif DemoVersionWithoutTimeLimit
strVersionDesc = "Demo Version of Supergame Plus";
#elif OEMVersion
strVersionDesc = "Supergame Plus, distributed under license";
#else
strVersionDesc = "The original Supergame Plus!!";
#endif
Console.WriteLine( strVersionDesc );
...
Diagnostic directives produce user-defined compile-time warning and error messages.
The following is the syntax of the diagnostic directives. The messages are strings, but notice that unlike normal C# strings, they do not have to be enclosed in quotation marks.
#warning Message
#error Message
When the compiler reaches a diagnostic directive, it writes out the associated message. The diagnostic directive messages are listed by the compiler along with any compiler-generated warning and error messages.
For example, the following code shows an #error
directive and a #warning
directive.
#error
directive is inside an #if
construct so that it will be generated only if the conditions on the #if
directive are met.#warning
directive is a reminder to the programmer to come back and clean up a section of code.#define RightHanded
#define LeftHanded
#if RightHanded && LeftHanded
#error Can't build for both RightHanded and LeftHanded
#endif
#warning Remember to come back and clean up this code!
Line number directives can do several things, including the following:
The syntax for the #line
directives is the following:
#line integer // Sets line number of next line to value of integer
#line "filename" // Sets the apparent filename
#line default // Restores real line number and filename
#line hidden // Hides the following code from stepping debugger
#line // Stops hiding from debugger
The #line
directive with an integer parameter causes the compiler to consider that value to be the line number of the following line of code. Numbering of the subsequent lines continues, based on that line number.
default
as the parameter.The following code shows examples of the line number directives:
#line 226
x = y + z; // Now considered by the compiler to be line 226
...
#line 330 "SourceFile.cs" // Changes the reported line number and filename
var1 = var2 + var3;
...
#line default // Restores true line numbers and filename
The region directive allows you to mark, and optionally name, a section of code. The characteristics of the #region
directive are the following:
#endregion
directive, further down in the code.Although region directives are ignored by the compiler, they can be used by source code tools. Visual Studio, for example, allows you to easily hide or display regions.
As an example, the following code has a region called Constructors
, which encloses the two constructors of class MyClass
. In Visual Studio, you could collapse this region to a single line when you didn't want to see it in the code and then expand it again when you needed to work on it or add another constructor.
#region Constructors
MyClass()
{
...
}
MyClass(string s)
{
...
}
#endregion
Regions can be nested, as shown in Figure 23-3.
Figure 23-3. Nested regions
The #pragma
warning
directive allows you to turn off warning messages and to turn them back on.
disable
form with a comma-separated list of warning numbers you want to turn off.restore
form with a list of the warning numbers you want to turn back on.For example, the following code turns off two warning messages: 618 and 414. Further down in the code, it turns on messages for 618 but leaves the messages for 414 turned off.
If you use either form without a warning number list, the command then applies to all warnings. For example, the following code turns off, and then restores, all warning messages.
3.144.39.133