SoFunction
Updated on 2025-03-07

Detailed explanation of preprocessor instructions in C#

Table of contents

1. #define and #undef
2. #if, #elif, #else and #endif
3. #warning and #error
4. #region and #endregion
5. #line
6. #pragma

There are many commands in C# called "Preprocessor Directives". These commands are never translated into commands in executable code, but they affect all aspects of the compilation process.

For example, using preprocessor instructions can prohibit the compiler from compiling a part of the code. These preprocessor instructions can be used if you plan to release two versions of the code, the basic version and the enterprise version with more features. When compiling a basic version of the software, using preprocessor instructions can prohibit the compiler from compiling code related to additional functions.

In addition, preprocessor instructions can also be used when writing code that provides debugging information. In fact, when selling software, you generally do not want to compile this part of the code.

The preprocessor instructions have the symbol # at the beginning.

C++ developers should know that preprocessor instructions are very important in C and C++, but in C#, there are not so many preprocessor instructions and they are not used too frequently. C# provides other mechanisms to implement the functions of many C++ instructions, such as custom features. It should also be noted that C# does not have a standalone preprocessor like C++, and the so-called preprocessor instructions are actually processed by the compiler.

Nevertheless, C# retains some preprocessor instruction names because these commands will make people feel like they are preprocessors.

The following briefly introduces the functions of preprocessor instructions.

 
1. #define and #undef

The usage of #define is as follows: #define DEBUG

It tells the compiler that there is a symbol for the given name, in this case DEBUG. This is a bit similar to declaring a variable, but this variable does not have a real value, it just exists.

This symbol is not part of the actual code, but only exists when the compiler compiles the code. It doesn't make any sense in C# code.


#undef is exactly the opposite - it deletes the definition of symbols: #undef DEBUG

If the symbol does not exist, #undef has no effect. Similarly, if the symbol already exists, #define does not work either. The #define and #undef commands must be placed at the beginning of the C# source file before declaring the code for any object to be compiled.

#define itself is useless, but it is very powerful when used in combination with other preprocessor instructions (especially #if).

Here we should pay attention to some changes in general C# syntax. The preprocessor instruction does not end with a semicolon, generally there is only one command on one line. This is because for preprocessor instructions, C# no longer requires commands to be separated by semicolons. If it encounters a preprocessor instruction, it assumes that the next command is on the next line.


2. #if, #elif, #else and #endif
These instructions tell the compiler whether to compile a block of code. Consider the following method:

Copy the codeThe code is as follows:

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

This code compiles as usual, but the command is included in the #if clause.

This line of code is executed only after the previous #define command defines the symbol DEBUG.

When the compiler encounters a #if statement, it will first check whether the relevant symbol exists. If the symbol exists, compile the code in the #if clause. Otherwise, the compiler ignores all code until a matching #endif directive is encountered.

Generally, the symbol DEBUG is defined during debugging, and the debug-related code is placed in the #if clause. After debugging is completed, comment out the #define statement, all debugging code will miraculously disappear, the executable file will become smaller, and the end user will not be confused by this debugging information (obviously, more testing is required to make sure the code works without a DEBUG defined).

This technology is very common in C and C++ programming, and is called conditional compilation.

The #elif (=else if) and #else directives can be used in #if blocks, and their meaning is very intuitive. You can also nest #if blocks:

Copy the codeThe code is as follows:

#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

 
Unlike in C++, using #if is not the only way to conditionally compile code, C# also provides another mechanism through the Conditional feature.

#if and #elif also support a set of logical operators "!", "==", "!=", and "||". If the symbol exists, it is considered true, otherwise it is false, for example:

Copy the codeThe code is as follows:

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

3. #warning and #error

The other two very useful preprocessor directives are #warning and #error, which generate warnings or errors respectively when the compiler encounters them. If the compiler encounters a #warning directive, the text behind the #warning directive will be displayed to the user, and the compilation will continue. If the compiler encounters a #error directive, the following text will be displayed to the user as a compilation error message, and then the compilation will be immediately exited, and the IL code will not be generated.

Use these two instructions to check whether the #define statement is doing something wrong. Use the #warning statement to remind yourself to perform an operation:

Copy the codeThe code is as follows:

#if DEBUG && RELEASE
#error "You've defined DEBUG and RELEASE simultaneously!"
#endif
#warning "Don't forget to remove this line before the boss tests the code!"
("*I hate this job.*");

4. #region and #endregion
The #region and #endregion directives are used to mark a piece of code as a block with a given name, as shown below.

Copy the codeThe code is as follows:

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

This doesn't seem to be of use, it doesn't affect the compilation process. The advantage of these directives is that they can be recognized by certain editors, including the Visual Studio .NET editor. These editors can use these instructions to make the code better layout on the screen.

5. #line
The #line directive can be used to change the file name and line number information displayed by the compiler in warnings and error messages. This instruction is not used much.

If you use some software packages to change the entered code when writing code before sending the code to the compiler, you can use this directive because it means that the line number or file name reported by the compiler does not match the line number or edited file name in the file.

The #line directive can be used to restore this match. You can also use the syntax #line default to restore the line number to the default line number:

Copy the codeThe code is as follows:

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

6. #pragma
The #pragma directive can suppress or restore specified compile warnings. Unlike command-line options, the #pragma directive can be executed at the class or method level, providing more granular control over what the suppression warning is and how long it is suppressed.

The following example disables the "field not used" warning, and then restores the warning after compiling the MyClass class.

Copy the codeThe code is as follows:

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

Thank you everyone for watching, thank you dear.