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

时间:2020-03-05 18:39:03  来源:igfitidea点击:

假设我们正在使用的对象具有与之关联的其他对象的集合,例如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
}

我的问题是,我们认为第二种选择是多么糟糕的编程实践,为什么?与整个集合的循环相比,它的性能如何?

解决方案

回答

如果在编写代码时希望该对象位于集合中,然后在运行时发现它不是,则将其称为例外情况,并且使用异常是适当的。

但是,如果我们只是在测试对象的存在,发现它不存在,那不是例外。在这种情况下使用异常是不合适的。

对运行时性能的分析取决于所使用的实际集合以及搜索它的方法。那没关系。不要让优化的幻想欺骗我们编写令人困惑的代码。

回答

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

在速度上,这取决于常见情况。如果我们更有可能找到该元素,那么异常解决方案将更快。如果我们更有可能失败,那么这将取决于集合的大小及其迭代速度。无论哪种方式,我们都希望在担心这种速度之前,先对正常使用情况进行衡量,看看这是否真的是瓶颈。首先要澄清一下,后一种解决方案比前一种要清晰得多。

回答

我将不得不多考虑我喜欢它的多少……我的直觉是,嗯,不是那么多...

编辑:我同意莱恩·福克斯(Ryan Fox)对特殊案件的评论是完美的

至于性能,它取决于集合上的索引器。 Clet会覆盖索引器运算符,因此,如果它像我们将要编写的contains方法那样执行for循环,则它会同样慢(由于try / catch可能会慢几纳秒...但不必担心除非该代码本身处于一个巨大的循环中)。

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

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

回答

通常,将异常处理用于程序流和逻辑是不好的做法。我个人认为,后一种选择是对异常的不可接受使用。考虑到当今常用语言的功能(例如C语言中的Linq和lambda),没有理由不编写自己的Contains()方法。

归根结底,如今,大多数集合确实已经包含了一个contains方法。因此,我认为大多数情况下这不是问题。

回答

我不得不说这是非常糟糕的做法。尽管有些人可能会高兴地说循环遍历集合对于引发异常的效率较低,但引发异常却有开销。我还想问一问,当我们更适合使用字典或者哈希表时,为什么要使用集合通过键访问项。

但是,此代码的主要问题是,无论引发何种异常,我们总是会得到相同的结果。

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

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

以下是几篇不错的文章,介绍了通常在何时何地引发异常:

  • 编程基础
  • 在C#中引发异常

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

It is important that exceptions are
  thrown only when an unexpected or
  invalid activity occurs that prevents
  a method from completing its normal
  function. Exception handling
  introduces a small overhead and lowers
  performance so should not be used for
  normal program flow instead of
  conditional processing. It can also be
  difficult to maintain code that
  misuses exception handling in this
  way.

回答

一般的经验法则是避免将异常用于控制流,除非会触发异常的情况是"异常",例如,极为罕见!

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

由于所涉及的所有开销,异常非常非常慢,因此,如果发生的次数足够多,那么也会有性能原因。

回答

例外应该是例外。

诸如"由于数据库从其下方掉落而丢失了该集合"之类的东西很特殊

字典的正常行为是"键不存在"。

对于winforms控件集合的特定示例,"控件"属性具有" ContainsKey"方法,这是我们应该使用的方法。

没有" ContainsValue",因为在处理字典/哈希表时,没有快速的方法可以遍历整个集合,检查是否存在某些东西,因此我们实在不建议这样做。

至于为什么例外应该是例外,这是两件事

  • 指示代码正在尝试执行的操作。我们希望使代码尽可能与其匹配,以使其易于阅读和维护。异常处理增加了一堆多余的东西,从而阻碍了此目的
  • 简短的代码。我们希望代码以最直接的方式执行其操作,因此它是可读性和可维护性的。再次,异常处理添加的杂项阻碍了这种方式。

回答

看看来自Krzystof的博客文章:http://blogs.msdn.com/kcwalina/archive/2008/07/17/ExceptionalError.aspx

应该将异常用于传达错误条件,但不应将它们用作控制逻辑(尤其是当存在确定条件的简单得多的方法时,例如Contains)。

问题的一部分在于,尽管抛出异常并不昂贵,但捕获起来却很昂贵,并且所有异常都在某个时刻被捕获。