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

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

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

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

有什么方法可以做到以下一个或两个:

  • 让Visual Studio告诉我究竟哪个引用引发了NullReferenceException
  • 如果失败了,有没有办法让调试器逐步通过lambda表达式(或者只是将鼠标hover在东西上以查看它们的值),因为Any正在评估它?

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

编辑:

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

编辑2:

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

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

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

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

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

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

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

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

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

看看你的代码,我可以看到三个表达式中只有一个可能导致了NullRef, r.RegistrationCountryDatesWorked

将这三个表达式放在Watch窗口中,并要求调试器中断任何NullReferenceException(通过Debug-> Exceptions),或者在lambda表达式中放置一个断点,并使其成为条件断点,条件为r == null || r.RegistrationCountry == null || DatesWorked == null r == null || r.RegistrationCountry == null || DatesWorked == null r == null || r.RegistrationCountry == null || DatesWorked == null ,答案应该很快出现。

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

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

 static Expression> WithNullDebugging(Expression> 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#中进行元编程,所以我不确定,但我认为这很有可能; 聪明的人让我知道如果不是,我会删除或修复这个答案。

直接的问题是lambda在一个语句中包含了很多复杂的逻辑,所以你找不到崩溃发生的地方。

但那只是副作用。 真正的问题是你的代码错误地假设没有任何引用是null。

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

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

这样,您将能够以一种您知道为空知识的forms重写整个表达式,并且您将不需要调试它以找出它爆炸的原因。

例如,这个:

  r.RegistrationCountry.IsUSAOrCandada() && 

如果r == null或r.RegistrationCountry == null,则可能导致空解除引用。 代码需要检查这些可能性。 “最具防御性”的代码是检查每个引用,如下所示:

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

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

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

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

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