Wednesday, August 30, 2006 10:04 PM
bart
Conditional compilation in C# - explaining System.Diagnostics.ConditionalAttribute
Introduction
Conditional compilation is one of the much unknown powerful features that are available in .NET and more specifically in the C#, VB.NET and J# compilers. In this post, I'd like to show you how to take benefit from this feature, applied to C#.
An example
using
System;
using System.Diagnostics;
class Program
{
static void Main(string[] args)
{
DebugLog("Before loop");
for (int i = 0; i < 10; i++)
Console.WriteLine(i);
DebugLog("After loop");
}
[Conditional("DEBUG")]
private static void DebugLog(string s)
{
Console.WriteLine(s);
}
}
First compile the program using one of the following options:
- In Visual Studio 2005 with the active configuration set to Debug.
- On the command-line using csc /define:DEBUG Program.cs. (Note: this is not the same as csc /debug+ Program.cs)
- Add #define DEBUG in the program code.
When you execute the program, you'll see:
Before loop
0
1
2
3
4
5
6
7
8
9
After loop
In the IL code you'll see something like this:
IL_0000: nop
IL_0001: ldstr "Before loop"
IL_0006: call void Program::DebugLog(string)
IL_000b: nop
IL_000c: ldc.i4.0
IL_000d: stloc.0
IL_000e: br.s IL_001b
IL_0010: ldloc.0
IL_0011: call void [mscorlib]System.Console::WriteLine(int32)
IL_0016: nop
IL_0017: ldloc.0
IL_0018: ldc.i4.1
IL_0019: add
IL_001a: stloc.0
IL_001b: ldloc.0
IL_001c: ldc.i4.s 10
IL_001e: clt
IL_0020: stloc.1
IL_0021: ldloc.1
IL_0022: brtrue.s IL_0010
IL_0024: ldstr "After loop"
IL_0029: call void Program::DebugLog(string)
IL_002e: nop
IL_002f: ret
Nothing special going on despite the declaration of the [Conditional("DEBUG")] attribute. However, let's compile the same application now as a release build:
- In Visual Studio 2005 with the active configuration set to Release.
- On the command-line using csc Program.cs.
Now the output will be:
0
1
2
3
4
5
6
7
8
9
and the corresponding IL is:
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: br.s IL_0010
IL_0005: ldloc.0
IL_0006: call void [mscorlib]System.Console::WriteLine(int32)
IL_000b: nop
IL_000c: ldloc.0
IL_000d: ldc.i4.1
IL_000e: add
IL_000f: stloc.0
IL_0010: ldloc.0
IL_0011: ldc.i4.s 10
IL_0013: clt
IL_0015: stloc.1
IL_0016: ldloc.1
IL_0017: brtrue.s IL_0005
IL_0019: ret
So, no single call to the DebugLog method was emitted in the Main's code. Powerful isn't it? The coolest thing about all this is that you don't have to decorate your code using a bunch of #if statements, like this:
static void Main(string[] args)
{
#if DEBUG
DebugLog("Before loop");
#endif
for (int i = 0; i < 10; i++)
Console.WriteLine(i);
#if DEBUG
DebugLog("After loop");
#endif
}
One advantage of the latter approach might be that you can also #if DEBUG ... #endif the DebugLog method itself. Using conditional compilation the DebugLog method will get compiled no matter what.
A few remarks to conclude:
- The ConditionalAttribute can only be applied to methods and to attribute classes (that is: classes that derive from System.Attribute). Thus property and event accessors can't be decorated using this attribute.
- The Debug and Trace classes of the .NET Framework use the ConditionalAttribute. So you don't have to worry about any performance hit whatsoever when you call various methods of these classes as a debugging aid. These calls just won't make it in the release build (or better: non-debug builds), e.g.:
[Conditional("DEBUG")]
public static void Assert (
bool condition
)
- The C++ compiler doesn't support the ConditionalAttribute; you'll have to rely on #if conditionals to include/exclude debugging code.
- The ConditionalAttribute allows multiple decorations per method (or attribute class):
[AttributeUsageAttribute(AttributeTargets.Class|AttributeTargets.Method, AllowMultiple=true)]
Therefore you can do things such as (cf. section 17.4.2.2 of the C# specification):
[Conditional("ALPHA")]
[Conditional("BETA")]
private static void DebugLog(string s)
- Attribute decorations are emitted to the metadata of a class. Because of this, you can get ConditionalAttribute working across languages and assemblies. An example of a class definition in C#:
public static class Helper
{
[Conditional("DEBUG")]
public static void DebugLog(string s)
{
Console.WriteLine(s);
}
}
and its usage in VB:
Class Program
Shared Sub Main()
Helper.DebugLog("Before loop")
Dim i As Integer
For i = 0 To 10
Console.WriteLine(i)
Helper.DebugLog("After loop")
End Sub
End Class
This will yield the same result as the C# only example, because the VB compiler can find out (using the metadata of class Helper) that calls to DebugLog should only be made when a DEBUG build is made.
References
Enjoy!
Del.icio.us |
Digg It |
Technorati |
Blinklist |
Furl |
reddit |
DotNetKicks
Filed under: Visual Studio 2005, .NET Framework v2.0, C# 2.0