无用的异常(NullReferenceException,KeyNotFoundException)

时间:2013-09-17 15:42:51

标签: c# exception nullreferenceexception keynotfoundexception

我发现C#异常非常烦人,因为它们提供的信息更少。这是什么原因?

NullReferenceException或KeyNotFoundExceptions很难调试,有时你不会在stacktrace中得到一个亚麻布。为什么异常本身不能提供更多信息?

例如:

private Dictionary<string, object> Properties = null;

public void Process(string key)
{ 
    var item = this.Properties[key];
    ....
}

当“Properties”为null时,我得到一个NullReferenceException:

  

“System.NullReferenceException:对象引用未设置为对象的实例”

为什么我没有得到:

  

“System.NullReferenceException:对象引用'属性'未设置为对象的实例”

这会更有用,CLR确实知道,哪个引用为空。

当我传递一个不存在的密钥时,例如过程( “伪”):

  

“System.Collections.Generic.KeyNotFoundException:字典中没有给定的键”

为什么我没有得到:

  

“System.Collections.Generic.KeyNotFoundException:词典中没有'dummy'

CLR知道哪个密钥已通过但未找到。

我尝试在生产环境中调试此类错误(传递非法密钥)  并使代码更健壮,如:

private Dictionary<string, object> Properties = null;

public void Process(string key)
{ 
  if (this.Properties != null)
  {
    if (this.Properties.ContainsKey(key))
    {
        var item = this.Properties[key];
        ...
    }
    else
    {
        throw new KeyNotFoundException(string.Format("The key '{0}' was not found.", key));
    }
  }
  else
  {
    throw new NullReferenceException(string.Format("The object 'Properties' is null."));
  }
}

但为什么我必须这样做,通常CLR可以告诉我细节出了什么问题。当发生异常时,我无法将这样的所有代码包装起来以获取更多信息。

5 个答案:

答案 0 :(得分:3)

对于你的KeyNotFoundException,你可以创建自己的字典类来抛出更有意义的消息(我建议你尝试扩展KeyNotFoundException并扔掉它),或者你可以使用TryGetValue并抛出一个有意义的例外。

然而,NullReferenceException更复杂:你假设CLR知道这个东西被称为Properties,但它不是那么简单:考虑this.GetSomething(abc).DoSomething(),其中{ {1}}返回null。什么是null的对象?它没有名字。有时候,特别是在Release优化代码中,会生成变量或其他可能为null的东西的名称。

您应该使用测试用例,Asserts,断点和其他调试模式代码进行调试,而不是期望您始终可以获得足够好的异常消息来调试生产代码。例如。即使您知道密钥GetSomething(abc)已被传入,您可能也没有足够的信息来了解为什么该密钥错误或已被传入。

答案 1 :(得分:1)

问题:为什么例外提供的信息少于可用信息?

答案:可能的性能问题。

要制作精彩的KeyNotFoundException,您需要致电ToString()。这可能需要相当长的时间。例如,如果我创建Dictionary<StringBuilder, int>,则无法找到密钥会导致(可能很大的)新内存块被分配并填充数据(在.NET 4.5实现中)。此外,这可能导致异常消息包含几兆字节的文本。

问题:为什么不在调试模式下更详细地生成异常消息?

答案:不一致是不好的。

修复只能在发布模式下重现的错误是非常困难的。仅在调试模式下获取键的字符串表示将导致不同的行为:

  1. ToString可以抛出异常。

  2. 懒惰的程序员可以对异常消息进行预检。

  3. 问题但是有一些关于UserVoice的建议可以改进消息。

    答案:如果您喜欢,请投票给他们。

    我认为NullReferenceException链中有意义的a.b.c.d.e.f.g.h()不是最优先考虑的问题。这很烦人,但它有一个简单的解决方法。如果你认为这是最重要的问题之一,那就投票吧。但你可以环顾四周,找到更多有趣的东西投票。

答案 2 :(得分:1)

通常,抛出异常的代码不知道将对消息执行什么操作。从概念上讲,异常可能包含MessageDeveloperPrivateMessage属性,并指定异常应避免在Message中放入任何可能危及安全性的信息(如果显示为一般公众和接收例外的代码应该避免以任何方式坚持DeveloperPrivateMessage将其暴露给不受信任的人员,但这会使事情变得复杂。相反,抛出异常的代码应该简单地避免包含任何机密数据。由于字典无法知道密钥是否可用于存储机密数据,这意味着密钥不应包含在异常消息中。

对于NullReferenceException,这通常是由陷阱处理程序捕获的,当mov eax,[esi+8]寄存器为零时,陷阱处理程序可能能够确定代码正在尝试执行ESI但是陷阱处理程序无法知道ESI中的值来自何处。如果有人说meh = Foo.Bar.BoomBar属性仅在修改null后返回Foo,以便 next 调用不会返回null,那么当系统尝试访问null.Boom时,Foo.Bar将不为空。系统可以做的最好的事情就是说“某个对象的成员Bar为空”,但这可能不是很有帮助。

答案 3 :(得分:0)

然后你应该添加特殊处理来获取这些信息。记住一切都需要付出一些代价。您进行异常处理(首先不应该发生)的膨胀程度越大 - 框架对于像这样的每个案例都必须越大。

你真的应该考虑这些“最后机会异常”,因为如果编码正确,你永远不应该看到它们。在异常消息和提供的堆栈跟踪之间 - 这应该是您的足够信息。

答案 4 :(得分:0)

检查null或者缺少密钥的责任在于您,即开发人员。原因是优化;如果默认异常更像你想要的更详细,那么编译器将无法有效地进行优化。

这是一种权衡。添加空检查可能很烦人,但这就是设计语言的方式。