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

标签 c# null-coalescing-operator

当我运行此代码时,我遇到了意外的 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.");
            }
        }

当我在调试器中单步执行代码时,在我跨过第一行(使用 ?? 运算符)之后 fileSystem 仍然具有值 null,因为在此屏幕截图中显示(跨过下一行会抛出 NullReferenceException): When is null not null?

这不是我想要的!我期待 null 合并运算符发现参数为 null 并创建一个 new FileSystemHelper()。我盯着这段代码看了很多年,看不出它有什么问题。

ReSharper 指出该字段仅在这一种方法中使用,因此可能会转换为局部变量...所以我尝试了一下,猜猜是什么?有效。所以,我有我的解决办法,但我无法终生明白为什么上面的代码不起作用。我觉得我正处于学习一些关于 C# 的有趣事物的边缘,或者我做了一些非常愚蠢的事情。谁能看到这里发生了什么?

最佳答案

我用下面的代码在VS2012中重现了它:

public void Test()
{
    TestFoo();
}

private Foo _foo;

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

public class Foo
{
}

如果您在 TestFoo 方法的末尾设置断点,您会期望看到 _foo 变量设置,但它在调试器中仍显示为 null .

但是,如果您随后对 _foo 执行任何操作,它就会正确显示。即使是简单的任务,例如

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

如果您单步执行它,您会看到 _foo 在分配给 f 之前显示为 null。

这让我想起了延迟执行行为,例如 LINQ,但我找不到任何可以证实这一点的东西。

这完全有可能只是调试器的一个怪癖。也许拥有 MSIL 技能的人可以阐明幕后发生的事情。

同样有趣的是,如果您将 null 合并运算符替换为等效运算符:

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

那么它就不会表现出这种行为。

我不是汇编/MSIL 专家,但看看这两个版本之间的反汇编输出就很有趣:

        _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 

将其与内联 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 

基于此,我确实认为存在某种延迟执行。与第一个示例相比,第二个示例中的赋值语句非常小。

关于c# - 为什么空合并运算符 (??) 在这种情况下不起作用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19352130/

相关文章:

javascript - ASP.NET MVC 在离开当前页面时放弃 session

c# - 如何判断一个Type是不是RunTimeType?

c# - 否定空合并运算符

c# - .NET Reflector 是否无法正确反射(reflect) null 合并运算符?

c# - 使用 Code-First MVC5 EF6 在 SQL 表中存储 DateTime 属性而不是字节数组

c# - 如果一整年过去了,则运行代码

c# - Unicode 字符串

vb.net - VB.NET中的合并运算符和条件运算符

javascript - 为什么 JavaScript 的空合并运算符 (||) 不适用于 es6 变量 (let/const)?

ruby - C# ?? Ruby 中的运算符?