Reputation: 11762
Using the following code as an example:
if (true)
{
string foo = null;
List<string> bar = new List<string>
{
"test"
};
bar.Any(t => t == foo);
}
If I run this program in a regular way (without a break point or any other interruption), everything works without exception or error (as you would expect it).
Now if I put a break point on the if statement and move the cursor to the curly brace as described in the following picture (using my mouse, not using F10, so skipping the if(true)
statement):
I get an exception of type System.NullReferenceException
when the debugger executes the statement string foo = null
It seems to be linked to the fact that the variable foo
is used in the lambda expression inside the if
statement. I have tested and reproduced this on Visual Studio 2012 and 2013 (pro and ultimate).
Any idea on why this could be happening?
Upvotes: 7
Views: 910
Reputation: 2442
Eric's answer and comments already describe why it can happens in general. I'd like to highlight whats going on in this particular case.
Here is a generated IL:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 3
.locals init (
[0] class [mscorlib]System.Collections.Generic.List`1<string> bar,
[1] class [mscorlib]System.Collections.Generic.List`1<string> <>g__initLocal0,
[2] class StackOverflow.Program/<>c__DisplayClass2 CS$<>8__locals3,
[3] bool CS$4$0000)
L_0000: nop
L_0001: ldc.i4.0
L_0002: stloc.3
L_0003: newobj instance void StackOverflow.Program/<>c__DisplayClass2::.ctor()
L_0008: stloc.2
L_0009: nop
L_000a: ldloc.2
L_000b: ldnull
L_000c: stfld string StackOverflow.Program/<>c__DisplayClass2::foo
L_0011: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
L_0016: stloc.1
L_0017: ldloc.1
L_0018: ldstr "test"
L_001d: callvirt instance void [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
L_0022: nop
L_0023: ldloc.1
L_0024: stloc.0
L_0025: ldloc.0
L_0026: ldloc.2
L_0027: ldftn instance bool StackOverflow.Program/<>c__DisplayClass2::<Main>b__1(string)
L_002d: newobj instance void [mscorlib]System.Func`2<string, bool>::.ctor(object, native int)
L_0032: call bool [System.Core]System.Linq.Enumerable::Any<string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, bool>)
L_0037: pop
L_0038: nop
L_0039: ret
}
Note L_0003
line. It calls ctor for auto-generated c__DisplayClass2
class, which hold foo
field, since you use it in a lambda. So NullReferenceException
happens because your skip class initialization, but then you assigning instance's field foo
on a line L_000c
.
Too bad, there is no easy way to debug on IL level to verify this, but we can debug JITed program (Debug -> Disassembly)
Here is your first breakpoint:
And then after cursor move:
One of these skiped call instructures must be call to ctor from L_0003
.
Upvotes: 8
Reputation: 660159
The comments which conjecture that you are skipping the generation of the closure are correct. C# programs are not guaranteed to have any particular behavior when you move the instruction pointer. If it hurts when you do that, don't do it.
actually that is a small lie. There are guarantees. For example, you are guaranteed that doing so in a verifiable program will not corrupt the internal data structures of the clr. You are guaranteed that doing so will not misalign the stack. and so on. But no guarantees are expressed or implied wrt your data structures! You move the instruction pointer at your peril.
Upvotes: 9