8

想象一下,您正在使用的一个对象具有与其关联的其他对象的集合,例如 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
}

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

4

8 回答 8

4

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

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

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

于 2008-08-12T00:30:43.390 回答
3

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

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

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

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

这些是关于何时何地抛出异常通常被认为可以接受的几篇不错的文章:

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

仅当发生阻止方法完成其正常功能的意外或无效活动时才引发异常,这一点很重要。异常处理会引入少量开销并降低性能,因此不应将其用于正常程序流而不是条件处理。维护以这种方式滥用异常处理的代码也可能很困难。

于 2008-08-12T00:26:37.577 回答
0

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

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

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

于 2008-08-12T00:01:59.110 回答
0

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

编辑:瑞恩福克斯对例外情况的评论是完美的,我同意

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

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

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

于 2008-08-12T00:07:06.027 回答
0

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

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

于 2008-08-12T00:10:19.297 回答
0

例外应该是例外。

诸如“由于数据库已从其下方掉出而导致集合丢失”之类的内容非常特殊

像“密钥不存在”这样的东西是字典的正常行为。

对于您的 winforms Control 集合的特定示例,该Controls属性有一个ContainsKey方法,这是您应该使用的。

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

至于为什么异常应该是异常的,大约有两件事

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

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

于 2008-08-12T02:02:49.723 回答
0

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

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

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

于 2008-08-17T14:38:19.963 回答
-2

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

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

于 2008-08-12T00:03:04.957 回答