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.

Related Articles