Tom Goff's .Net Musings

Tidbits of information regarding .Net, C#, and SQL Server.

Archive for December 2007

COTW: ConditionalAttribute Class

leave a comment »

The ConditionalAttribute class allows you to selectively include/exclude methods or classes based on a given condition. Before we show how to use the ConditionalAttribute class, we will describe conditions and how to define them.

A condition is simply a boolean flag with a given name. If a condition is defined, meaning it has been included or set, then it can be considered true. If it is not defined, then it can be considered false. Conditions can vary depending on the current configuration (e..g Debug, Release, etc) and the platform (e.g. Any CPU, x64, x86, etc). Based on these conditions, you can selectively include/exclude sections of code in your project.

These conditions are used by the compiler to include/exclude code from the final assembly. Since the conditions are used by the complier they must be defined at compile time and are not valid after the assembly has been built. There are two ways to define conditions in Visual Studio 2005. In addition, condition can be defined using the command-line build tools.

In Visual Studio 2005, there are two standard conditions: DEBUG and TRACE. By default, Debug builds define both of these conditions and Release builds only define the TRACE condition. In addition, you can define any number of additional conditions using the Project properties or in source code. The screen shot below shows the Project properties window where conditions can be defined:

ConditionalAttribute (Debug settings)

As you can see, the DEBUG and TRACE are defined by default and you can define your own custom conditions (MYCON1 and MYCON2). If we switch to the Release build, you can see that the conditions are different:

ConditionalAttribute (Release settings)

You can define conditions in source code using the #define directive. The example below shows how to define MYCON1 and MYCON2 in source code:

#define MYCON1
#define MYCON2

using System;
//...

The #define directives must be included before any other non-directive lines, such as the using statements. If you are coming from a C/C++ background then #define are nothing new, but there is one difference from the C/C++ counterpart. The #define in C# does not assign a value, it can only be used to define conditions. In C/C++, #define could be used to define conditions (a.k.a. symbols) and a value could also be associated with it.

NOTE: If you are using the command-line build tools, then you must define the DEBUG and TRACE conditions manually (unless you are building from the project).

Now that we know how to define conditions, let’s see how we can use them. There are two ways to check for conditions: #if directive and ConditionalAttribute. We will first look at the #if directive and it’s associated directives. Assume we have the following application:

using System;
using System.Diagnostics;

namespace ConditionalTestApp {
    class Program {
        static void Main(string[] args) {
            CheckLicense();
            // ... Run application ...
        }

        static void CheckLicense() {
            Console.WriteLine("Check License called");
            // ... Check license ...
        }
    }
}

In addition, assume we do not want to check the license if we are running a debug build (for whatever reason). Then we can use the #if directive to exclude the call to CheckLicense:

using System;
using System.Diagnostics;

namespace ConditionalTestApp {
    class Program {
        static void Main(string[] args) {
#if !DEBUG
            CheckLicense();
#endif
            // ... Run application ...
        }

        static void CheckLicense() {
            Console.WriteLine("Check License called");
            // ... Check license ...
        }
    }
}

This tells the compiler that the call to CheckLicense should not be included in the final assembly. Using Reflector, we can see that the call was excluded in the final debug assembly:

ConditionalAttribute (Reflector #if)

The #if directive requires a matching #endif directive to indicate the end of the block. In addition, you can use the #else and #elif directives to provide multiple paths or sections of code. Conditions can also be undefined using the #undef directive. The #if directive supports ! (not), && (and), || (or), and other boolean operations on the conditions. When using these directives, Visual Studio is smart enough to “gray out” any code that will not be included in the current build.

ConditionalAttribute (Visual Studio #if)

The ConditionalAttribute class is really just a simplified version of the #if directive. Anything that can be accomplished with ConditionalAttribute can be accomplished using the #if directive, with one minor exception that I will describe later. Using the #if directive to replace the ConditionalAttribute would take more work (in the form of typing), and would be harder to maintain.

The ConditionalAttribute class can only be used to check for the existence of a condition. It cannot be used to check if a condition is NOT defined, as we did in the example above using the #if directive. In order to modify our application to use the ConditionalAttribute instead of the #if directive, we need to define a new condition. We will call the condition CHECKLICENSE and we will define it only for the Release build. The new condition is defined in the Project properties and the updated source code is:

using System;
using System.Diagnostics;

namespace ConditionalTestApp {
    class Program {
        static void Main(string[] args) {
            CheckLicense();
            // ... Run application ...
        }

        [Conditional("CHECKLICENSE")]
        static void CheckLicense() {
            Console.WriteLine("Check License called");
            // ... Check license ...
        }
    }
}

Now if we look at the assembly in Reflector we will see that the call to CheckLicense has been removed from the debug build.ConditionalAttribute (Reflector attribute)

You can see that the ConditionalAttribute on the CheckLicense method is visible above as well. This is the one exception I alluded to earlier. This allows you to see, after the build, under what conditions the given method or class would be included or excluded.

With both our examples above, the CheckLicense method and it’s code are still included in the final assembly. Only calls to the given method where excluded. This may be a problem if you are trying to hide sensitive debug code from your users (e.g. how to bypass licensing). You could use the #if directive to exclude the code inside the CheckLicense method. This could be done in conjunction with using the ConditionalAttribute, to exclude the method calls.

Advertisements

Written by Tom

December 3, 2007 at 8:16 am