Search code examples
c#lambdavisual-studio-debugging

Debugger stepping 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 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):

enter image description here

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?


Solution

  • 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:

    enter image description here

    And then after cursor move:

    enter image description here

    One of these skiped call instructures must be call to ctor from L_0003.