Quantcast
Channel: Martijn's C# Programming Blog » Beginner
Viewing all articles
Browse latest Browse all 10

C# Preprocessor Directives Explained

$
0
0

By using pre-processor directives you can exclude parts of your code from being seen by the compiler. Excluded from the assembly they are never seen at run-time. This is different from a regular if (x) {} block where code is actually compiled in, evaluated at runtime and added to the assembly.

To understand why you would want to do this, a little history: One of the good things about C is that it works on every imaginable platform, the bad thing was of course that it works on every imaginable platform. There was always a little tweaking required to get your code to compile. Your program might have needed to compile on Amiga, DOS , OS/2, Windows or Linux. The invention of the preprocessor made this much easier. As a separate step prior to compilation it combs through the source code and modifies it according to the pre-processing instructions found in the source code. The C compiler never gets to see the things that don’t apply to it.

In C# there is no separate preprocessor step instead the compiler itself skims through the code as it reads it. Fortunately it is also nowhere near as complex as the C pre-processor.

Contents

The #define and #undef directives

The basis of the C# preprocessor directives are #define and #undef. They allow you to create and destroy symbols used by the preprocessing step.

#define TESTVERSION
#undef DEBUG

A common use is for a programmer to turn on and off the inclusion of debugging code. When you are coding and debugging this allow you to test your code but this code shouldn’t be present in the production version of your software.

It is possible to set symbols using /define on the compilers command line allowing you to build scripts that produce different versions of your code. For example a “full version” and a limited “demo” version.

C# does not have any pre-defined symbols, except for maybe DEBUG

If you are used to your C/C++ compiler setting large numbers of symbols so that you could identify the compiler and version you will puzzled to discover the C# doesn’t have any. So you cannot check for the .NET version from a pre-defined symbol and branch your code based on that. (The closest that you can do is to check for the execution environment at runtime, for example if you want to know if you a running under mono.)

If you are compiling your code in the Visual C# / MonoDevelop environment there will be one symbol defined you can use: DEBUG. This is set in the project options and if you are building your code you can select if you want to build the Debug or Release version.

Unlike C and C++ there are no macros in C#

The following is not going to fly on C#. Macro’s are commonly used to expand expressions while compiling C code. A common problem is that C doesn’t have a boolean type. So the first thing most programmers do when starting a new project is to define their own as shown in the next little C code snippet:

#include <stdio.h>

#define BOOL  int
#define TRUE   1
#define FALSE  0
#define IS_TRUE(x) (x == TRUE)

void main()
{
        if (IS_TRUE(TRUE))
        {
          printf("It is TRUE!");
        }
}

When the C preprocessor comes across this it would replace “BOOL” with int, FALSE with 0 and TRUE with 1. The compiler never saw the BOOL, or the TRUE or FALSE. But lets not hang around here — as said, this is not supported by C#

Conditional Compilation with #if / #else / #endif

Of course, if you can define symbols you need to be able to test for them as well. The #if / #else / #endif directives do just that. If you are building the debug version of your code, you can check for the DEBUG symbol:

#if DEBUG
Console.WriteLine(“Things are not going so well!”);
#endif

If you are not building the Debug version of your code — the above line is never seen by the compiler, and thus is not included in the assembly. All #if statements need to be closed by an #endif, and of course you can also use an #else statement:

#if DEBUG
Console.WriteLine(“Things are not going so well, some more debugging is needed!”);
#else
Console.WriteLine(“Fatal Error: Please call free support (0800) 123 123″);
#endif

The #eif directive is a short form for “#else #if #endif”, it works the same but you can save some space as you do not need to end with another #endif. If your project has a more complicated release schedule with alpha, beta and production releases you could try this:

#if (!RELEASE)
Console.WriteLine(“This is not a release version”);
#if (BETA)
Console.WriteLine(“Beta, for limited release only”);
#eif (ALPHA)
Console.WriteLine(“Alpha, for internal testing only”);
#endif
#else
Console.WriteLine(“Welcome to Widgets 1.0″);
#endif

As can be seen you can apply several operators to the symbols: ! (not),== (equality), != (inequality), && (and) and || (or).

Avoid the clutter – use conditional attributes instead

Even a small program has many potential spots where you might like to introduce a debug statement or a log function. If you have to put each of them into a seperate #if DEBUG / #endif block they will start to take up a lot of space in your code. C# has its own slightly more elegant solution to this problem: conditional attributes. They are included in the System.Diagnostics namespace.

In the following program the “LogLine” function will only ever run if the “DEBUG” symbol has been set.

using System;
using System.Diagnostics;

namespace MyNameSpace
{
    class MainClass
    {
        [ Conditional("DEBUG") ]
        public static void LogLine(string msg,string detail)
        {
            Console.WriteLine("Log: {0} = {1}",msg,detail);
        }

        public static void Main(string[] args)
        {
            int Total = 0;
            for(int Lp = 1; Lp < 10; Lp++)
            {
                LogLine("Total",Total.ToString());
                Total = Total + Lp;
            }
        }
    }
}

You can also define combinations chained together with OR to decide if the code is enabled:

[ Conditional("ALPHA"),Conditional("BETA") ]
public static void LogLine(string msg,string detail)

Conditionals work differently from the #if/#endif directives that in the above example the “LogLine” code will still be included in the assembly. It will never get called however. The compiler will ensure that the LogLine method isn’t called. In fact if you use a dissasembler (like monodis) the code for Main will look like this:

        public static void Main(string[] args)
        {
            int Total = 0;
            for(int Lp = 1; Lp < 10; Lp++)
                Total = Total + Lp;
        }

Using #error and #warning during compilation

C# provides the #error and #warning directives to output messages during compilation. As you can expect, if the compiler encounters an #error it will report the problem and stop compiling. The #warning directive just causes a log entry to be displayed in the build output.

#if !DEBUG
#warning This code is NOT production ready
#endif

Generated code builds and error reporting with #line

In regular coding there are not many uses for #line. This directive is mostly useful when you build a code generator that turns one source file into another. Microsoft uses this for example for ASP.NET. Normally if something breaks during such an intermediate step, the compiler would report the error as being in the intermediate file.

The #line directive can force the compiler to report the error as coming from a different line numer, or even a completely different source file.

        public static void Main(string[] args)
        {

#line 250
#if DEBUG
#error This code is NOT production ready
#endif

Even though in my original test code the warning was given at line 19 in the source file, the #line directive forces the compiler to report 250 instead.

Region Directives

The Visual Studio suite, and the newer MonoDevelop versions allow for outlining of code blocks. You click the little (+) next to the code and it expands the class or method. The #region and #endregion directives allow you to define your own outlined blocks.

#region myregion
        public static void Main(string[] args)
        {
            int Total = 0;
            for(int Lp = 1; Lp < 10; Lp++)
{
                LogLine("Total",Total.ToString());
                Total = Total + Lp;
            }
        }
#endregion

This is a post from Martijn's C# Coding Blog.


Viewing all articles
Browse latest Browse all 10

Trending Articles