Why doesn't the null coalescing operator (??) work in this situation?

前端 未结 2 1253
隐瞒了意图╮
隐瞒了意图╮ 2021-01-11 09:19

I\'m getting an unexpected NullReferenceException when I run this code, omitting the fileSystemHelper parameter (and therefore defaulting it to nul

相关标签:
2条回答
  • 2021-01-11 09:41

    Someone else experienced the same problem in this question. Interestingly it is also using a this._field = expression ?? new ClassName(); format. It could be some kind of issue with the debugger, as writing the value out seemed to produce the correct results for them.

    Try adding debug/log code to show the value of the field after assignment to eliminate weirdness in the attached debugger.

    0 讨论(0)
  • 2021-01-11 09:47

    I have reproduced it in VS2012 with the following code:

    public void Test()
    {
        TestFoo();
    }
    
    private Foo _foo;
    
    private void TestFoo(Foo foo = null)
    {
        _foo = foo ?? new Foo();
    }
    
    public class Foo
    {
    }
    

    If you set a breakpoint at the end of the TestFoo method, you would expect to see the _foo variable set, but it will still show as null in the debugger.

    But if you then do anything with _foo, it then appears correctly. Even a simple assignment such as

    _foo = foo ?? new Foo();
    var f = _foo;
    

    If you step through it, you'll see that _foo shows null until it is assigned to f.

    This reminds me of deferred execution behavior, such as with LINQ, but I can't find anything that would confirm that.

    It's entirely possible that this is just a quirk of the debugger. Perhaps someone with MSIL skills can shed some light on what is happening under the hood.

    Also interesting is that if you replace the null coalescing operator with it's equivalent:

    _foo = foo != null ? foo : new Foo();
    

    Then it does not exhibit this behavior.

    I am not an assembly/MSIL guy, but just taking a look at the dissasembly output between the two versions is interesting:

            _foo = foo ?? new Foo();
    0000002d  mov         rax,qword ptr [rsp+68h] 
    00000032  mov         qword ptr [rsp+28h],rax 
    00000037  mov         rax,qword ptr [rsp+60h] 
    0000003c  mov         qword ptr [rsp+30h],rax 
    00000041  cmp         qword ptr [rsp+68h],0 
    00000047  jne         0000000000000078 
    00000049  lea         rcx,[FFFE23B8h] 
    00000050  call        000000005F2E8220 
            var f = _foo;
    00000055  mov         qword ptr [rsp+38h],rax 
    0000005a  mov         rax,qword ptr [rsp+38h] 
    0000005f  mov         qword ptr [rsp+40h],rax 
    00000064  mov         rcx,qword ptr [rsp+40h] 
    00000069  call        FFFFFFFFFFFCA000 
    0000006e  mov         r11,qword ptr [rsp+40h] 
    00000073  mov         qword ptr [rsp+28h],r11 
    00000078  mov         rcx,qword ptr [rsp+30h] 
    0000007d  add         rcx,8 
    00000081  mov         rdx,qword ptr [rsp+28h] 
    00000086  call        000000005F2E72A0 
    0000008b  mov         rax,qword ptr [rsp+60h] 
    00000090  mov         rax,qword ptr [rax+8] 
    00000094  mov         qword ptr [rsp+20h],rax 
    

    Compare that to the inlined-if version:

            _foo = foo != null ? foo : new Foo();
    0000002d  mov         rax,qword ptr [rsp+50h] 
    00000032  mov         qword ptr [rsp+28h],rax 
    00000037  cmp         qword ptr [rsp+58h],0 
    0000003d  jne         0000000000000066 
    0000003f  lea         rcx,[FFFE23B8h] 
    00000046  call        000000005F2E8220 
    0000004b  mov         qword ptr [rsp+30h],rax 
    00000050  mov         rax,qword ptr [rsp+30h] 
    00000055  mov         qword ptr [rsp+38h],rax 
    0000005a  mov         rcx,qword ptr [rsp+38h] 
    0000005f  call        FFFFFFFFFFFCA000 
    00000064  jmp         0000000000000070 
    00000066  mov         rax,qword ptr [rsp+58h] 
    0000006b  mov         qword ptr [rsp+38h],rax 
    00000070  nop 
    00000071  mov         rcx,qword ptr [rsp+28h] 
    00000076  add         rcx,8 
    0000007a  mov         rdx,qword ptr [rsp+38h] 
    0000007f  call        000000005F2E72A0 
            var f = _foo;
    00000084  mov         rax,qword ptr [rsp+50h] 
    00000089  mov         rax,qword ptr [rax+8] 
    0000008d  mov         qword ptr [rsp+20h],rax 
    

    Based on this, I do think there is some kind of deferred execution happening. The assignment statement in the second example is very small in comparison to the first example.

    0 讨论(0)
提交回复
热议问题