Visual Studio可以告诉我哪个引用引发了NullReferenceException?

时间:2011-12-06 22:26:18

标签: c# visual-studio debugging resharper nullreferenceexception

我正在为MVC Web应用程序编写单元测试,并且我一直在获取null引用异常,因为模拟的测试对象仅部分初始化。我知道哪一行抛出异常,它看起来像这样:

return Supervisor.RegistrationInformation.Registrations
    .Any(r =>
        r.RegistrationCountry.IsUSAOrCandada() &&
        (!DatesWorked.Start.HasValue || r.RegistrationDate <= DatesWorked.Start.Value) &&
        (!DatesWorked.End.HasValue || r.RegistrationExpirationDate >= DatesWorked.End.Value) &&
        //...

那里有很多参考文献,其中任何一个都可能是问题所在。但是,NullReferenceException本身似乎并未捕获哪个参考文献爆炸。我传入lambda的事实提出了另一个挑战:据我所知,我无法在调试期间单步执行lambda并查看r的哪些成员为空。

我有什么方法可以做以下其中一项或两项:

  • 让Visual Studio确切地告诉我哪个引用提出了NullReferenceException
  • 如果失败了,有没有办法让调试器逐步完成lambda表达式(或者只是将鼠标悬停在其中以查看它们的值),因为Any正在评估它?

我觉得必须有办法做这些事情,但我似乎无法找到它。我使用的是VS2010 Premium,我安装了Resharper,VS Power Tools和其他一些扩展程序。如果有一个附加组件可以做到这一点,我会很好。

编辑:

正如Eric Lippert指出的那样,当代码在Release配置中编译时,无法确定NR异常的来源。我只是询问在调试模式下工作。如果Visual Studio(或VS的某些扩展)可以在调试时跟踪引用的来源,那将回答我的问题。

编辑2:

第二个问题 - 如何打破并逐步完成lambda - 已经得到了解答,但我仍然想知道是否有一种自动方法来追踪空引用。

8 个答案:

答案 0 :(得分:17)

通常没有办法做你想做的事,不。要了解原因,请考虑抛出空引用异常时发生的情况。想象一下你是编译器,你必须发出代码来处理对abc.Def.Ghi.Jkl()的调用,其中abc是本地的,Def和Ghi是引用类型的字段,而Jkl是一个方法。没有IL指令可以做一些复杂的事情;你必须打破它。因此,您为相同的程序发出代码,其中一切都更简单。您发出程序片段:

temp1 = abc.Def;
temp2 = temp1.Ghi;
temp2.Jkl();

假设temp2为null,因为Ghi为null。在调用Jkl之前不会发现这个事实,此时抛出异常的事情并不知道temp2是如何初始化的。很久以前发生的事情,过去的纳秒和机器代码对过去没有记忆; null引用没有在它上面留下一个说明null来自何处的注释,就像你说“a = b + c”时一样,得到的数字12并没有保留一条说明“我是b和c的总和“。

答案 1 :(得分:3)

这不会解决您的整个问题,但它应该有所帮助:

你可以在lambda中设置一个断点 - 只是不按照通常的方式(点击装订线将断开包含的语句,而不是lambda的内部)。你必须将光标放在lambda中并点击F9 - 然后你将在lambda中得到一个断点。

答案 2 :(得分:2)

针对您的特定问题的一个解决方案是在多行中重写lambda,逐个评估每个条件并明确返回。然后,您可以更轻松地跟踪它并找到null参考。

答案 3 :(得分:1)

如果在此处设置断点,则应该能够在执行该行之前检查每个值并抛出异常。您只需要有条不紊地查看每个取消引用的项目,直到找到Null项。 Visual Studio非常擅长此类事情。

答案 4 :(得分:1)

可以在lambda表达式中放置一个断点,当它命中时,你应该可以将鼠标悬停在表达式上并查看它们的值。

查看您的代码,我可以看到三个表达式中只有一个可能导致NullRef- rr.RegistrationCountryDatesWorked

将这三个表达式放在Watch窗口中,并要求调试器中断任何NullReferenceException(通过Debug-&gt; Exceptions),或者在lambda表达式中放置一个断点,并使其成为条件的条件断点{ {1}},答案应该很快出现。

答案 5 :(得分:1)

我通常不会给出一个非答案的答案,但我认为答案说没有一般的方法来做到这一点是不正确的。

在我看来,你可以编写一个包装函数来获取表达式树,分解具有属性和字段访问器的每个子表达式,并使用显式空值检查重构它,从而在每个访问器上引发信息性异常。伪代码:

static Expression<Func<T, bool>> WithNullDebugging(Expression<Func<T, bool>> exp)
{
    for each node in the expression tree
        if node is a field, property, or method accessor
            generate a null check for this member and an exception throw
            substitute the checked node for this node
        else if the node has subexpression children
            call this method recursively on each child
            substitute each checked subexpression for the subexpression

    return the fixed expression tree
}

我通常不会在C#中进行元编程,所以我不确定,但我认为这很有可能;有人聪明地告诉我,如果不是,我会删除或修复这个答案。

答案 6 :(得分:1)

当前的问题是lambda在一个语句中包含了很多复杂的逻辑,所以你找不到崩溃的位置。

但这只是副作用。 真正的问题是您的代码假设错误,没有任何引用将为null。

一种方法是尝试将崩溃分开,并在“破坏的位”上加上绷带。但这不会攻击问题的根源:代码中存在未经检查的假设,并且您已经证明其中至少有一个是错误的。如果另一个是错误的,那么在未来某个未定义的位置,您的程序可能会再次崩溃,并且您将再次进行调试和包扎。这可以继续,你的代码每次都会被黑客攻击。

您需要放下调试器并考虑代码。所有代码,一次通过。 “检查”它:贯穿表达的每个部分并问自己“这一点可以为空吗?如果是这样会发生什么?如果是这样,我怎样才能使它安全?”

这样,您将能够以一种您知道为空知识的形式重写整个表达式,并且您将不需要调试它来解决它为什么会爆炸。

例如,这个:

    r.RegistrationCountry.IsUSAOrCandada() && 

...如果r == null或r.RegistrationCountry == null,则可能导致空取消引用。代码需要检查这些可能性。 “最具防御性”的代码是检查每个引用类似的东西:

    r != null && r.RegistrationCountry != null && r.RegistrationCountry.IsUSAOrCandada() && 

保证只有在上一步成功时才执行每一步。但请注意,列表可能永远不会提供r == null,因此可能不需要检查。或者r.RegistrationCountry可能是一个struct(一个不可为空的类型),所以你会知道check是不需要的。因此,您可以通过思考来避免不必要的检查。但是你需要仔细考虑代码的每个部分来挑战并消除所有的假设。

答案 7 :(得分:0)

是的。您需要在visual studio中安装Resharper扩展。 Resharper是连续代码质量分析工具。

您可以在以下链接中找到更多详细信息

https://www.jetbrains.com/resharper/