When the provided example is built in release mode and then JIT-ed into 64-bit machine code, it does not contain enough information for the debugger to correlate the breakpoint with any particular machine instruction. That’s why debugger never stops at this breakpoint during execution of a JIT-ed machine code. It just does not know where to stop. Probably it is some kind of misbehavior or even a bug in 64-bit CLR debugger because it is reproducible only when it is JIT-ed into 64-bit machine code but not into 32-bit machine code.
When the debugger sees a breakpoint in your code it tries to find out a machine instruction in the JIT-ed code that corresponds to the location marked by the breakpoint. First, it needs to find an IL instruction that corresponds to a breakpoint location in your C# code. Then it needs to find a machine instruction that corresponds to the IL command. Then it sets a real breakpoint on the found machine instruction and starts execution of the method. In your case, it looks like that the debugger just ignores a breakpoint because it cannot map it to a particular machine instruction.
The debugger cannot find an address of a machine instruction that immediately follows if…else statement. The if…else statement and the code inside it somehow causes this behavior. It does not matter what statement follows the if…else. You can replace the Console.WriteLine(“2”) statement with some other one and you will be still able to reproduce the issue.
You will see that the C# compiler emits a try…catch block around the logic that reads the list if you will disassemble the resulting assembly with Reflector. It is a documented feature of the C# compiler. You can read more about it at The foreach statement
A try…catch…finally block has a pretty invasive effect on a JIT-ed code. It uses the Windows SEH mechanism under the hood and rewrites your code badly. I cannot find a link to a good article right now but I’m sure that you can find one out there if you are interested.
It is what happens here. The try…finally block inside of if…else statement causes the debugger to hiccup. You can reproduce your issue with a much simple code.
bool b = false;
if (b)
{
try
{
b = true;
}
finally
{
b = true;
}
}
else
{
b = true;
}
b = true;
This code does not call any external functions (it eliminates effect of method inlining proposed by one of the answers) and it compiles directly into IL without any additional coded added by the C# compiler.
It is reproducible only in release mode because in the debug mode the compiler emits the IL NOP instruction for every line of your C# code. The IL NOP instruction does nothing and it is directly compiled to the CPU NOP instruction by the JITer that does nothing too. The usefulness of this instruction is that it can be used by the debugger as an anchor for breakpoints even if the rest of the code is badly rewritten by the JITer.
I was able to make the debugger to work correctly by putting one NOP instruction right before the statement that follows the if…else.
You can read more about NOP operations and debugger mapping process here Debugging IL
You can try to use WinDbg and SOS extension for it to examine JIT-ed version of the method. You can try to examine machine code that JIT-er generates and try to understand why it cannot map back that machine code to particular line of C#.
Here are couple link about using WinDbg for breaking in managed code and getting a memory address of a JIT-ed method. I believe that you should be able to find a way to get JIT-ed code for a method from there: Setting a breakpoint in WinDbg for Managed Code, SOS Cheat Sheet (.NET 2.0/3.0/3.5).
You can also try to report an issue to Microsoft. Probably this is a CLR debugger bug.
Thank you for the interesting question.