M9L1L1 Directives
M9L1L1 Directives
Pre-processor Directives
Introduction
Pre-processor directives, such as #define and #ifdef, are typically used to make source programs
easy to change and easy to compile in different execution environments.
Objectives
Upon completion of this unit, you will be able to:
List and identify the pre-processor directives.
Pre-processor Directives
Directives in the source file tell the pre-processor to perform specific actions. For example, the preprocessor can replace tokens in the text, insert the contents of other files into the source file, or
suppress compilation of part of the file by removing sections of text. Pre-processor lines are
recognized and carried out before macro expansion. Therefore, if a macro expands into something
that looks like a pre-processor command, that command is not recognized by the pre-processor.
Pre-processor statements use the same character set as source file statements, with the exception
that escape sequences are not supported. The character set used in pre-processor statements is
the same as the execution character set. The pre-processor also recognizes negative character
values.
#define
#warning
#line
#region
#endregion
#pragma
#pragma warning
#pragma checksum
Lecture Name
#define:
Lets you define a symbol. When you use the symbol as the expression that is passed to the #if
directive, the expression will evaluate to true. For example:
#define DEBUG
The #define directive cannot be used to declare constant values as is typically done in C and C++.
Constants in C# are best defined as static members of a class or struct. If you have several such
constants, consider creating a separate "Constants" class to hold them.
Symbols can be used to specify conditions for compilation. You can test for the symbol with either
#if or #elif. You can also use the conditional attribute to perform conditional compilation.
You can define a symbol, but you cannot assign a value to a symbol. The #define directive must
appear in the file before you use any instructions that are not also directives.
You can also define a symbol with the /define compiler option. You can undefine a symbol with
#undef.
A symbol that you define with /define or with #define does not conflict with a variable of the same
name. That is, a variable name should not be passed to a pre-processor directive and a symbol can
only be evaluated by a pre-processor directive.
The scope of a symbol created by using #define is the file in which it was defined.
#warning
Lets you generate a level one warning from a specific location in your code. For example:
#warning Deprecated code in this method.
A common use of #warning is in a conditional directive. It is also possible to generate a userdefined error with #error
Example:
// pre-processor_warning.cs
// CS1030 expected
#define DEBUG
Lecture Name
class MainClass
{
static void Main()
{
#if DEBUG
#warning DEBUG is defined
#endif
}
}
#line
#line lets you modify the compiler's line number and (optionally) the file name output for errors and
warnings. This example shows how to report two warnings associated with line numbers. The
#line 200 directive forces the line number to be 200 (although the default is #7) and until the next
#line directive, the filename will be reported as "Special". The #line default directive returns the line
numbering to its default numbering, which counts the lines that were renumbered by the previous
directive.
class MainClass
{
static void Main()
{
#line 200 "Special"
int i;
int j;
#line default
char c;
// CS0168 on line 9
float f;
// CS0168 on line 10
Lecture Name
#line hidden // numbering not affected
string s;
double d; // CS0168 on line 13
}
}
The #line directive might be used in an automated, intermediate step in the build process. For
example, if lines were removed from the original source code file, but you still wanted the compiler
to generate output based on the original line numbering in the file, you could remove lines and then
simulate the original line numbering with #line.
The #line hidden directive hides the successive lines from the debugger, such that when the
developer steps through the code, any lines between a #line hidden and the next #line directive
(assuming that it is not another #line hidden directive) will be stepped over. This option can also be
used to allow ASP.NET to differentiate between user-defined and machine-generated code.
Although ASP.NET is the primary consumer of this feature, it is likely that more source generators
will make use of it.
A #line hidden directive does not affect file names or line numbers in error reporting. That is, if an
error is encountered in a hidden block, the compiler will report the current file name and line number
of the error.
The #line filename directive specifies the file name you want to appear in the compiler output. By
default, the actual name of the source code file is used. The file name must be in double quotation
marks ("") and must be preceded by a line number.
A source code file can have any number of #line directives.
The following example shows how the debugger ignores the hidden lines in the code. When you run
the example, it will display three lines of text. However, when you set a break point, as shown in the
example, and hit F10 to step through the code, you will notice that the debugger ignores the hidden
line. Notice also that even if you set a break point at the hidden line, the debugger will still ignore it.
// pre-processor_linehidden.cs
using System;
class MainClass
{
static void Main()
{
Lecture Name
Console.WriteLine("Normal line #1."); // Set break point here.
#line hidden
Console.WriteLine("Hidden line.");
#line default
Console.WriteLine("Normal line #2.");
}
}
#region
#region lets you specify a block of code that you can expand or collapse when using the outlining
feature of the Visual Studio Code Editor. In longer code files, it is convenient to be able to collapse
or hide one or more regions so that you can focus on the part of the file that you are currently
working on. The following example shows how to define a region:
Lecture Name
#endregion
#endregion marks the end of a #region block. For example:
#region MyClass definition
class MyClass
{
static void Main()
{
}
}
#endregion
#pragma
#pragma gives the compiler special instructions for the compilation of the file in which it appears.
The instructions must be supported by the compiler. In other words, you cannot use #pragma to
create custom pre-processing instructions. The Microsoft C# compiler supports the following two
#pragma instructions:
#pragma warning (C# Reference)
#pragma checksum (C# Reference)
#pragma pragma-name pragma-arguments
#pragma warning
#pragma warning can enable or disable certain warnings.
Lecture Name
Example:
// pragma_warning.cs
using System;
// CS3021
public class D
{
int i = 1;
public static void F()
{
}
}
#pragma checksum
Generates checksums for source files to aid with debugging ASP.NET pages.
Lecture Name
The Visual Studio debugger uses a checksum to make sure that it always finds the right source. The
compiler computes the checksum for a source file, and then emits the output to the program
database (PDB) file. The debugger then uses the PDB to compare against the checksum that it
computes for the source file.
This solution does not work for ASP.NET projects, because the computed checksum is for the
generated source file, rather than the .aspx file. To address this problem, #pragma checksum
provides checksum support for ASP.NET pages.
When you create an ASP.NET project in Visual C#, the generated source file contains a checksum
for the .aspx file, from which the source is generated. The compiler then writes this information into
the PDB file.
If the compiler encounters no #pragma checksum directive in the file, it computes the checksum and
writes the value to the PDB file.
Example:
class TestClass
{
static int Main()
{
#pragma
checksum
"file.cs"
8fceb2a794a2}" "{012345678AB}" // New checksum
"{3673e4ca-6098-4ec1-890f-
}
}
Summary
Upon completion of this unit, you will be able to:
List and identify the new features and improvements in the .NET Framework 4.