Debugger included in if statement and lambda expression

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 the usual way (without a breakpoint or any other interrupt), everything works without exception or error (as expected).

Now, if I put a breakpoint in an if statement and move the cursor in curly braces, as described in the following figure (using my mouse, not using F10, so skip the if(true) ):

enter image description here

I get an exception of type System.NullReferenceException when the debugger executes the string foo = null statement string foo = null

This seems to be due to the fact that the variable foo used in the lambda expression inside the if . I tested and reproduced this on Visual Studio 2012 and 2013 (pro and final).

Any idea on why this might be happening?

+6
source share
2 answers

Eric's answer and comments already describe why this could happen in general. I would like to emphasize what is happening in this particular case.

Here is the 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 . It calls ctor for the automatically generated class c__DisplayClass2 , which contains the foo field, since you use it in lambda. Thus, a NullReferenceException is due to the initialization of the skip class, but then you assign the instance field foo to line L_000c .

Too bad, there is no easy way to debug at the IL level to test this, but we can debug the JITed program (Debug -> Disassembly)

Here is your first breakpoint:

enter image description here

And then after moving the cursor:

enter image description here

One of these missed call instructions must be called on ctor from L_0003 .

+6
source

The comments that suggest that you are missing the closure generation are correct. When you move the instruction pointer to C # programs, no special behavior is guaranteed. If it hurts when you do it, don't do it.

this is actually a small lie. There are guarantees. For example, you are guaranteed that this is done in the program being checked, that will not damage the internal data structures of clr. You are guaranteed that this will not lead to a stack offset. and so on. But no guarantees are expressed or implied by your data structures! You move the instruction pointer to your level.

+8
source

Source: https://habr.com/ru/post/970823/


All Articles