当我运行此代码时,我得到一个意外的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#有趣的东西的边缘,无论是那个还是我做过一些非常愚蠢的事情。谁能看到这里发生了什么?
答案 0 :(得分:12)
我使用以下代码在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
。
这让我想起延迟执行行为,比如使用LINQ,但我找不到任何可以确认的行为。
完全有可能这只是调试器的一个怪癖。也许拥有MSIL技能的人可以了解幕后发生的事情。
同样有趣的是,如果用等效的替换空合并运算符:
_foo = foo != null ? foo : new Foo();
然后它没有表现出这种行为。
我不是汇编/ 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
将其与inlined-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
基于此,我确实认为存在某种延迟执行。与第一个示例相比,第二个示例中的赋值语句非常小。
答案 1 :(得分:1)
其他人在this question遇到了同样的问题。有趣的是,它也使用this._field = expression ?? new ClassName();
格式。它可能是调试器的某种问题,因为写出值似乎可以为它们产生正确的结果。
尝试添加调试/日志代码以在分配后显示字段的值,以消除附加调试器中的怪异。