Debug.WriteLine

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.

ildasm options

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.

Flattr this!