为什么下面的C#代码在可为空的DateTime初始化时会抛出NullReferenceException?

5

下面是一些简单的代码:

    static void Main(string[] args)
    {
        var coll = new List<string> {"test", "test2", "test3"};

        var filteredColl = coll.Select(x => x).ToList();

        if (!filteredColl.Any())
        {
            DateTime? date = new DateTime();

            filteredColl = coll.Where(x => date.GetValueOrDefault().Date.ToString(CultureInfo.InvariantCulture) == x).Select(x => x).ToList();
        }
    }

问题是,为什么以下步骤会导致NullReferenceException崩溃:
1)在if处设置断点
2)设置下一个执行点:
3)尝试使用F10继续执行:
如果我注释掉代码的最后一行,它就不会崩溃。
更新:以下是堆栈跟踪:
System.NullReferenceException was unhandled   HResult=-2147467261  
Message=Object reference not set to an instance of an object.  
Source=ConsoleApplication28   StackTrace:
       at ConsoleApplication28.Program.Main(String[] args) in Program.cs: line 21
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()   InnerException:

我创建了一个全新的控制台项目,添加了代码之后,按照上述步骤仍然会崩溃。这是使用.NET Framework 4.5项目。 - Mikael Koskinen
我更新了帖子并添加了堆栈跟踪。可能是与Visual Studio 2012有关的问题? - Mikael Koskinen
你试图调试一个发布版本吗? - Moo-Juice
我没有使用任何奇怪的技巧 :) 应用程序的完整代码已经发布在上面。这是一个全新的控制台项目,使用F5运行(使用调试配置作为默认配置)。 - Mikael Koskinen
我也明白,如果你只是运行代码,它不会进入 if(..),你必须手动设置下一行。看到 new Datetime() 部分,MaxValueMinValue 有错误 - 无法解引用表达式。指针无效。如果你从 if(..) 中删除 not,它就可以正常运行,将该行放在 if(..) 块之外也可以。 - iabbott
显示剩余5条评论
2个回答

13

这是将执行点移动到声明了捕获变量作用域的上下文中的副作用。将其报告为IDE错误是合理的,但修复它并不是一件简单的事情。基本上,date 不是一个变量 - 它是一个捕获上下文中的字段,因为有 lambda 存在。编译器实际上执行以下操作:

if (!filteredColl.Any())
{
    var ctx = new SomeCaptureContext(); // <== invented by the compiler
    ctx.date = new DateTime();

    filteredColl = coll.Where(ctx.SomePredicate).Select(x => x).ToList();
}

其中SomePredicate表示:

class SomeCaptureContext {
    public DateTime? date; // yes, a public field - needs to support 'ref' etc
    public bool SomePredicate(string x) // the actual name is horrible
    {
        return this.date.GetValueOrDefault()
              .Date.ToString(CultureInfo.InvariantCulture) == x;
    }
}

问题在于当您将执行位置拖动到:

DateTime? date = new DateTime();

在IL术语中,你实际上是将它拖到了这条线:

ctx.date = new DateTime();

在此之前的捕获上下文行,即:

var ctx = new SomeCaptureContext();

从未执行,所以ctxnull。因此引发了NullReferenceException

将其记录为错误可能是合理的,但它是一个微妙的错误 - 你不一定总是希望拖动执行上下文来初始化捕获上下文 - 它必须是“如果它们是null”。


你的解释很好,但是如果你将执行点拖到 if (!filteredColl.Any()) 下面的左括号,你仍然会得到相同的异常。难道作用域不应该在那个括号之后声明吗? - Protector one

0

嗯...看起来真的很奇怪。 你的引用中是否有mscorlib和使用中是否有System? 也许你还有另一个类名为DateTime,它覆盖了System.DateTime。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接