C#: Fun with #line directive

#line pre-processor directive, though not commonly used is very interesting. This directive makes the compiler report line numbers for source files differently. If you have the directive #line 1 on the 3nd line of a source file then the compiler will interpret the next line (4th) as the 1st line and will emit all error messages and any other kind of output messages based on this modified line numbering. You can also make the compiler report the source filename differently by giving the name of a file within quotes after the line number.

If you try to compile the following piece of code

 /*  1 */ using System;/*  2 */           #line 1 "coolFile.cs" /*  4 */ namespace LineDirective                 // treated as 1/*  5 */ {                                       // treated as 2/*  6 */     class Program                       // treated as 3/*  7 */     {                                   // treated as 4/*  8 */         static void Main(string[] args) // treated as 5/*  9 */         {                               // treated as 6/* 10 */             Invalid syntax;              // treated as 7/* 11 */             throw new Exception("Bang"); // treated as 8/* 12 */         }/* 13 */     }/* 14 */ }

then even though the compilation failure happens for the junk string on the 10th line in the file program.cs the compiler will report failure on line 7 in the file coolFile.c

Error 1 The type or namespace name 'Invalid' could not be found (are you missing a using directive or an assembly reference?) c:\MyStuff\Code\C#\LineDirective\LineDirective\coolFile.cs 7 13 LineDirective

The compiler also stores this modified line numbering in the pdb and so if you comment out the junk string on the 10th line and build/run the program then an exception will be thrown on the 11th line. However you'll get the following in the console window.

Unhandled Exception: System.Exception: Bang
at LineDirective.Program.Main(String[] args) in C:\MyStuff\Code\C#\LineDirective\LineDirective\Program.cs:line 8

Interestingly here the file name is the correct filename and not the one we had passed. I'm not too sure on whether this is as expected or is a bug. I'll try to find this out.

Another interesting usage of #line directive is #line hidden. If you use this in a source file then all line information after this directive upto any other #line directive (other than another #line hidden) is skipped and not stored. Hence while stepping through code in the debugger these line will be skipped entirely.

In the following code

 namespace LineDirective{    class Program    {        static void Main(string[] args)        {            foo();        } #line hidden        static void foo()        {            Console.WriteLine("Bar");        } #line default    }}

if you try to step into the function foo in the debugger you'll not be able to do so as the entire function definition is inside a #line hidden block and hence line number information for the whole function is missing in the pdb.

How does this work?

The .pdb file generated by the compiler contains line number information to allow the debugger to step through source code. In this for blocks of IL code the corresponding source file and line number information is stored. The #line directive makes the compiler change the line number information it puts into this file. There is an understanding between the debugger and the compiler that if the line number of a block of IL is set as 16707566 ( 0xFeeFee) then the debugger will simply step over this block of code. This is utilized in #line hidden and the compiler translates hidden to 0xFeeFee You can try out the following code to see this in action

 using System;namespace LinePreProcDirective{    class Program    {        static void foo()        {            Console.WriteLine("foo"); #line 16707566Console.WriteLine("bar");  #line default        }        static void Main(string[] args)        {            foo();        }    }}

The code marked in bold will be skipped over if you try to step through this code in the debugger.