我发现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可以告诉我细节出了什么问题。当发生异常时,我无法将这样的所有代码包装起来以获取更多信息。
答案 0 :(得分:3)
对于你的KeyNotFoundException
,你可以创建自己的字典类来抛出更有意义的消息(我建议你尝试扩展KeyNotFoundException
并扔掉它),或者你可以使用TryGetValue
并抛出一个有意义的例外。
然而,NullReferenceException
更复杂:你假设CLR知道这个东西被称为Properties
,但它不是那么简单:考虑this.GetSomething(abc).DoSomething()
,其中{ {1}}返回null。什么是null的对象?它没有名字。有时候,特别是在Release优化代码中,会生成变量或其他可能为null的东西的名称。
您应该使用测试用例,Assert
s,断点和其他调试模式代码进行调试,而不是期望您始终可以获得足够好的异常消息来调试生产代码。例如。即使您知道密钥GetSomething(abc)
已被传入,您可能也没有足够的信息来了解为什么该密钥错误或已被传入。
答案 1 :(得分:1)
问题:为什么例外提供的信息少于可用信息?
答案:可能的性能问题。
要制作精彩的KeyNotFoundException
,您需要致电ToString()
。这可能需要相当长的时间。例如,如果我创建Dictionary<StringBuilder, int>
,则无法找到密钥会导致(可能很大的)新内存块被分配并填充数据(在.NET 4.5实现中)。此外,这可能导致异常消息包含几兆字节的文本。
问题:为什么不在调试模式下更详细地生成异常消息?
答案:不一致是不好的。
修复只能在发布模式下重现的错误是非常困难的。仅在调试模式下获取键的字符串表示将导致不同的行为:
ToString
可以抛出异常。
懒惰的程序员可以对异常消息进行预检。
问题但是有一些关于UserVoice的建议可以改进消息。
答案:如果您喜欢,请投票给他们。
我认为NullReferenceException
链中有意义的a.b.c.d.e.f.g.h()
不是最优先考虑的问题。这很烦人,但它有一个简单的解决方法。如果你认为这是最重要的问题之一,那就投票吧。但你可以环顾四周,找到更多有趣的东西投票。
答案 2 :(得分:1)
通常,抛出异常的代码不知道将对消息执行什么操作。从概念上讲,异常可能包含Message
和DeveloperPrivateMessage
属性,并指定异常应避免在Message
中放入任何可能危及安全性的信息(如果显示为一般公众和接收例外的代码应该避免以任何方式坚持DeveloperPrivateMessage
将其暴露给不受信任的人员,但这会使事情变得复杂。相反,抛出异常的代码应该简单地避免包含任何机密数据。由于字典无法知道密钥是否可用于存储机密数据,这意味着密钥不应包含在异常消息中。
对于NullReferenceException
,这通常是由陷阱处理程序捕获的,当mov eax,[esi+8]
寄存器为零时,陷阱处理程序可能能够确定代码正在尝试执行ESI
但是陷阱处理程序无法知道ESI中的值来自何处。如果有人说meh = Foo.Bar.Boom
,Bar
属性仅在修改null
后返回Foo
,以便 next 调用不会返回null,那么当系统尝试访问null.Boom
时,Foo.Bar
将不为空。系统可以做的最好的事情就是说“某个对象的成员Bar
为空”,但这可能不是很有帮助。
答案 3 :(得分:0)
然后你应该添加特殊处理来获取这些信息。记住一切都需要付出一些代价。您进行异常处理(首先不应该发生)的膨胀程度越大 - 框架对于像这样的每个案例都必须越大。
你真的应该考虑这些“最后机会异常”,因为如果编码正确,你永远不应该看到它们。在异常消息和提供的堆栈跟踪之间 - 这应该是您的足够信息。
答案 4 :(得分:0)
检查null或者缺少密钥的责任在于您,即开发人员。原因是优化;如果默认异常更像你想要的更详细,那么编译器将无法有效地进行优化。
这是一种权衡。添加空检查可能很烦人,但这就是设计语言的方式。