Conditional compilation symbols & Conditional attributes

Sometimes it would be necessary to produce different builds for the Visual Studio solution that you have defined. The produced builds may or may not execute some parts of the code. You can achieve this using Conditional compilation symbols and #if/#else/#elif/#endif compiler directives.

Using Visual Studio 2010 I have created a solution called Test that contains a Console Application called Test:

From Configuration Manager (right-click on Test solution and you should see it) I have selected the Debug configuration:

By this time the DEBUG and TRACE symbols should be automatically defined for your Test project. Just to double check right-click on Test project and select Properties and go to Build tab:

You can see that Visual Studio has taken care to define the DEBUG and TRACE symbols and I have defined the LUCKPP symbol.

Now lets just create a simple class with the following structure:


class Test
{
   static void Verify()
   {
#if DEBUG
      Console.WriteLine("Verify() has been executed for Debug configuration.");
#endif
   }

   static void Main(string[] args)
   {
      Verify();
   }
}

When compiled and executed in Debug mode the output should be obvious: Verify() has been executed for Debug configuration.
If we compile and execute in Release mode there should be no output since the DEBUG symbol is not defined for the Release configuration.

The interesting part is what does the compiler do when we compile in Debug mode and then in Release mode.

1. Compile in Debug mode

In this case the DEBUG symbol is defied and as a result of the compilation the produced assembly will contain a Verify method that makes a call to Console.WriteLine. Just to enforce this you can have a look at the Test.dll using ildasm. Below you can see the generated IL for the Verify() method, contained by the Test.dll:

2. Compile in Release mode

In this case as you might think the DEBUG symbol is not defined and the produced IL after the compilation will contain a Verify() method that has an empty body:

In this case, in the Main(…) method we have a call to an empty method. At run-time we will have a penalty since the JIT compiler will generate code for calling a method that actually does nothing.

3. Conditional Attributes

In order to fix this problem (calling an empty method) we have the possibility of using Conditional attributes. Below there is a more elegant way of writing code that will be or will not be executed depending on the symbols available at compile time


class Test
{
    [Conditional("DEBUG")]
    static void Verify()
    {
        Console.WriteLine("Verify() has been executed for Debug configuration.");
    }

    static void Main(string[] args)
    {
        Verify();
    }
}

When compiled in Debug mode, the result should be obvious (the Main(…) method will include a call to the Verify() method).
The surprise comes when we compile in Release mode. In this case the resulted assembly will contain IL code corresponding to the Verify() method but the Main(…) method will contain no calls to Verify() method. When we used the #if #endif directives we had to pay the cost of calling an empty method.

Below you can see the IL code that resulted when we used Conditional attributes and compiled in Release mode:

4. Conclusion

Using conditional attributes instead of preprocessor directives has several benefits:
– allows you to better structure your conditional compile code (since conditional attributes can be applied only to methods)
– using them generates more efficient code

Things to keep in mind when using condtitional attributes:
– avoid introducing side effects of conditional methods
– the return type of conditional methods should be void
– conditional methods shouldn’t have parameters

Leave a comment