I answered a question on StackOverflow last week which made me remember a few years ago when I also wondered, “Where does Debug.WriteLine go?!”
The answer is very simple (and no, I’m not peeved that I was the first to answer and didn’t get accepted).
If you look at the Debug.WriteLine documentation on MSDN, you will see a ConditionalAttribute:
[ConditionalAttribute("DEBUG")]
public static void WriteLine(
string message
)
This attribute is one of a few* compiler-time attributes. In other words, the compiler looks for this attribute specifically and, if found and the _DEBUG_ condition is met (i.e. ‘DEBUG’ is defined), THIS METHOD IS NOT COMPILED. That’s pretty awesome, but you don’t need to take my word for it. I’ve created a quick example.
Program.cs
Program.cs is a small program with three important pieces: Console.WriteLine, Debug.WriteLine, and a final Console.WriteLine. We have a before and after Console.WriteLine to show that Debug.WriteLine is not compiled into Program.exe when DEBUG is not defined.
using System;
using System.Diagnostics;
class Program {
static void Main(string[] args) {
Console.WriteLine("Console.WriteLine #1");
Debug.WriteLine("Debug.WriteLine");
Console.WriteLine("Console.WriteLine #2");
}
}
To compile normally, run
csc Program.cs
.
To compile with the DEBUG constant defined, run:
csc /define:DEBUG Program.cs
You can run Program.exe after both compiles, but you will see the same output.
The IL
You can use a tool called ildasm to dump the IL code of Program.exe (ildasm is part of the Windows SDK, after installation it can be found at, e.g., C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools).
Just open ildasm, choose File->Open and select Program.exe. Next, choose File->Dump. Choose ‘Dump IL Code’ and any other options you want.
Here is the output of the main method for both versions of the exe:
Program-NoDefines.il
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 24 (0x18)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Console.WriteLine #1"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ldstr "Console.WriteLine #2"
IL_0011: call void [mscorlib]System.Console::WriteLine(string)
IL_0016: nop
IL_0017: ret
} // end of method Program::Main
Program-DEBUGDefined.il
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 35 (0x23)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Console.WriteLine #1"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ldstr "Debug.WriteLine"
IL_0011: call void [System]System.Diagnostics.Debug::WriteLine(string)
IL_0016: nop
IL_0017: ldstr "Console.WriteLine #2"
IL_001c: call void [mscorlib]System.Console::WriteLine(string)
IL_0021: nop
IL_0022: ret
} // end of method Program::Main
As you can see, Debug.WriteLine is only added to Program.exe when we pass the DEFINE constant! Pretty cool, huh?
- The only other compiler-time attribute I know off-hand is ObsoleteAttribute, which will generate compiler warnings or errors if passing true as a second parameter into the constructor.