使用未处理的异常而不是 Contains()?

Posted

技术标签:

【中文标题】使用未处理的异常而不是 Contains()?【英文标题】:Using unhandled exceptions instead of Contains()? 【发布时间】:2010-09-05 17:16:23 【问题描述】:

想象一下,您正在使用的对象具有与其相关联的其他对象的集合,例如 WinForm 上的 Controls 集合。您想检查集合中的某个对象,但该集合没有Contains() 方法。有几种方法可以解决这个问题。

实现您自己的Contains() 方法,方法是遍历集合中的所有项目以查看其中一项是否是您要查找的。这似乎是“最佳实践”方法。 我最近遇到了一些代码,其中尝试访问 try 语句中的对象,而不是循环,如下所示:
try  
  
    Object aObject = myCollection[myObject];  
  
catch(Exception e)  
  
    //if this is thrown, then the object doesn't exist in the collection

我的问题是,您认为第二种选择的编程实践有多差,为什么?与遍历集合的循环相比,它的性能如何?

【问题讨论】:

【参考方案1】:

一般的经验法则是避免对控制流使用异常,除非触发异常的情况是“异常的”——例如,极其罕见!

如果这是正常且定期发生的事情,则绝对不应将其作为例外处理。

由于所涉及的所有开销,异常非常非常慢,因此也可能存在性能原因,如果它经常发生的话。

【讨论】:

【参考方案2】:

我不得不说这是非常糟糕的做法。虽然有些人可能会很高兴地说循环遍历集合在引发异常方面效率较低,但引发异常是有开销的。我还想问为什么您更适合使用字典或哈希表时使用集合来按键访问项目。

然而,我对这段代码的主要问题是,无论抛出什么类型的异常,你总是会得到相同的结果。

例如,由于集合中不存在该对象,或者因为集合本身为 null,或者因为您无法将 myCollect[myObject] 强制转换为 aObject,因此可能会引发异常。

所有这些异常都将以相同的方式处理,这可能不是您的意图。

这是几篇很好的文章,介绍了通常认为可以在何时何地抛出异常:

Foundations of Programming Throwing exceptions in c#

我特别喜欢第二篇文章中的这句话:

例外情况很重要 仅在发生意外或 发生阻止的无效活动 从完成其正常的方法 功能。异常处理 引入了少量开销并降低了 性能,所以不应该用于 正常的程序流程,而不是 有条件的处理。它也可以 难以维护的代码 在此滥用异常处理 方式。

【讨论】:

【参考方案3】:

如果在编写代码时,您希望这个对象在集合中,然后在运行时发现它不是,我会称之为例外情况,使用例外是正确的。

但是,如果您只是测试一个对象是否存在,而您发现它不存在,这并不例外。在这种情况下使用异常是不合适的。

运行时性能的分析取决于实际使用的集合,以及搜索它的方法。不过那应该没关系。不要让优化的错觉欺骗您编写令人困惑的代码。

【讨论】:

【参考方案4】:

我必须更多地考虑我有多喜欢它......我的直觉是,呃,不是那么多......

编辑:我同意 Ryan Fox 在特殊情况下的 cmets 是完美的

至于性能,它取决于集合上的索引器。 C# 允许您覆盖索引器运算符,因此如果它正在执行类似于您将编写的 contains 方法的 for 循环,那么它将同样慢(由于 try/catch 可能会慢几纳秒......但没有什么可做的)除非代码本身在一个巨大的循环中,否则担心)。

如果索引器是 O(1)(甚至 O(log(n))... 或任何比 O(n) 更快的索引器),那么 try/catch 解决方案当然会更快。

另外,我假设索引器正在抛出异常,如果它返回 null,您当然可以只检查 null 而不使用 try/catch。

【讨论】:

【参考方案5】:

一般来说,对程序流和逻辑使用异常处理是不好的做法。我个人觉得后一种选择是不可接受的使用异常。鉴于当今常用语言的特性(例如 C# 中的 Linq 和 lambdas),没有理由不编写自己的 Contains() 方法。

作为最后的想法,现在大多数集合确实已经有一个 contains 方法。所以我认为在很大程度上这不是问题。

【讨论】:

【参考方案6】:

例外应该是例外。

类似于“由于数据库从其下方脱落而导致集合丢失”之类的异常情况

像“key is not present”之类的东西是字典的正常行为。

对于您的 winforms 控件集合的具体示例,Controls 属性有一个 ContainsKey 方法,这是您应该使用的。

没有ContainsValue,因为在处理字典/哈希表时,没有快速的方法可以遍历整个集合,检查是否存在某些东西,所以你真的不鼓励这样做。

至于为什么 Exceptions 应该是例外的,它是关于 2 件事

    指示您的代码尝试执行的操作。你想让你的代码尽可能地匹配它试图实现的目标,因此它是可读和可维护的。异常处理增加了一堆额外的麻烦,阻碍了这个目的

    代码简洁。你希望你的代码以最直接的方式做它正在做的事情,所以它是可读和可维护的。再次,异常处理增加的麻烦阻碍了这一点。

【讨论】:

【参考方案7】:

看看 Krzystof 的这篇博文:http://blogs.msdn.com/kcwalina/archive/2008/07/17/ExceptionalError.aspx

应使用异常来传达错误条件,但不应将它们用作控制逻辑(尤其是当有更简单的方法来确定条件时,例如包含)。

问题的一部分是异常,虽然抛出成本不高,但捕获成本高,并且所有异常都会在某个时刻被捕获。

【讨论】:

【参考方案8】:

后者是可接受的解决方案。虽然我肯定会发现集合在这种情况下抛出的特定异常(ElementNotFound?)。

速度方面,这取决于常见情况。如果您更有可能找到该元素,则异常解决方案会更快。如果您更有可能失败,那么它将取决于集合的大小及其迭代速度。无论哪种方式,在担心这样的速度之前,您都需要衡量正常使用情况,看看这是否真的是一个瓶颈。先搞清楚,后一种方案比前一种方案要清晰得多。

【讨论】:

以上是关于使用未处理的异常而不是 Contains()?的主要内容,如果未能解决你的问题,请参考以下文章

Visual Studio 不会因用户未处理的异常而中断

在 TPL 中快速抛出未处理的异常

未处理的 JS 异常:找不到变量:进程

如何修复抛出的未处理异常

OWIN / Katana 的未处理异常全局处理程序?

需要处理未捕获的异常并发送日志文件