当我运行此代码时,我遇到了意外的 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
):
这不是我想要的!我期待 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/