简体   繁体   English

为什么空合并运算符(??)在这种情况下不起作用?

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

I'm getting an unexpected NullReferenceException when I run this code, omitting the fileSystemHelper parameter (and therefore defaulting it to null): 当我运行此代码时,我得到一个意外的NullReferenceException ,省略了fileSystemHelper参数(因此将其默认为null):

public class GitLog
    {
    FileSystemHelper fileSystem;

    /// <summary>
    ///   Initializes a new instance of the <see cref="GitLog" /> class.
    /// </summary>
    /// <param name="pathToWorkingCopy">The path to a Git working copy.</param>
    /// <param name="fileSystemHelper">A helper class that provides file system services (optional).</param>
    /// <exception cref="ArgumentException">Thrown if the path is invalid.</exception>
    /// <exception cref="InvalidOperationException">Thrown if there is no Git repository at the specified path.</exception>
    public GitLog(string pathToWorkingCopy, FileSystemHelper fileSystemHelper = null)
        {
        this.fileSystem = fileSystemHelper ?? new FileSystemHelper();
        string fullPath = fileSystem.GetFullPath(pathToWorkingCopy); // ArgumentException if path invalid.
        if (!fileSystem.DirectoryExists(fullPath))
            throw new ArgumentException("The specified working copy directory does not exist.");
        GitWorkingCopyPath = pathToWorkingCopy;
        string git = fileSystem.PathCombine(fullPath, ".git");
        if (!fileSystem.DirectoryExists(git))
            {
            throw new InvalidOperationException(
                "There does not appear to be a Git repository at the specified location.");
            }
        }

When I single step the code in the debugger, after I step over the first line (with the ?? operator) then fileSystem still has the value null, as shown in this screen snip (stepping over the next line throws NullReferenceException ): 当我单步执行调试器中的代码时,在我跳过第一行(使用??运算符)后, fileSystem仍然具有null值,如此屏幕fileSystem所示(踩到下一行抛出NullReferenceException ): 什么时候为null不为空?

This is not what I expected! 这不是我的预期! I'm expecting the null coalescing operator to spot that the parameter is null and create a new FileSystemHelper() . 我期望null合并运算符发现参数为null并创建一个new FileSystemHelper() I have stared at this code for ages and can't see what is wrong with it. 我已经盯着这段代码多年了,看不出它有什么问题。

ReSharper pointed out that the field is only used in this one method, so could potentially be converted to a local variable... so I tried that and guess what? ReSharper指出该字段仅用于这一方法,因此可能会被转换为局部变量......所以我试过这个并猜猜是什么? It worked. 有效。 So, I have my fix, but I cannot for the life of me see why the code above doesn't work. 所以,我有我的修复,但我不能为我的生活看到为什么上面的代码不起作用。 I feel like I am on the edge of learning something interesting about C#, either that or I've done something really dumb. 我觉得我正处于学习C#有趣的东西的边缘,无论是那个还是我做过一些非常愚蠢的事情。 Can anyone see what's happening here? 谁能看到这里发生了什么?

I have reproduced it in VS2012 with the following code: 我在VS2012中使用以下代码复制了它:

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. 如果在TestFoo方法的末尾设置断点,您可能会看到_foo变量集,但它仍将在调试器中显示为null。

But if you then do anything with _foo , it then appears correctly. 但是,如果你随后对_foo任何事情 ,那么它就会正确显示。 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 . 如果你单步执行它,你会看到_foo显示为null,直到它被赋值给f

This reminds me of deferred execution behavior, such as with LINQ, but I can't find anything that would confirm that. 这让我想起了延迟执行行为,例如LINQ,但我找不到任何可以证实的行为。

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. 也许拥有MSIL技能的人可以了解幕后发生的事情。

Also interesting is that if you replace the null coalescing operator with it's equivalent: 同样有趣的是,如果用等效的替换null合并运算符:

_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: 我不是汇编/ MSIL人,但只是看看两个版本之间的dissasembly输出很有意思:

        _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: 将其与内联if版本进行比较:

        _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. 与第一个示例相比,第二个示例中的赋值语句非常小。

Someone else experienced the same problem in this question . 其他人在这个问题上遇到了同样的问题。 Interestingly it is also using a this._field = expression ?? new ClassName(); 有趣的是,它也使用了this._field = expression ?? new ClassName(); 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. 尝试添加调试/日志代码以在分配后显示字段的值,以消除附加调试器中的怪异。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM