抛出异常的性能考虑

9
我经常遇到以下类型的代码,我想知道这是否是一个好的实践方法(从性能角度来看):
```

我经常遇到以下类型的代码,我想知道这是否是一个好的实践方法(从性能角度来看):

```
try
{
    ... // some code
}
catch (Exception ex)
{
    ... // Do something
    throw new CustomException(ex);
}

基本上,程序员所做的是将异常包含在自定义异常中,并再次抛出该异常。

这与以下两种情况有何不同:

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw ex;
}

或者
try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw;
}

放弃任何功能或编码最佳实践的争论,这三种方法之间是否存在性能差异?
8个回答

11

@Brad Tutterow

第一种情况下异常并没有消失,它被传递到了构造函数中。关于剩余的部分我同意你的看法,第二种方法会由于损失堆栈跟踪而非常糟糕。在我使用.NET时,我遇到很多其他程序员就是这样做的情况,当我需要查看异常的真正原因时,只能发现它从一个巨大的try块中重新抛出,而我现在不知道问题的来源在哪里。

我也赞成Brad的观点,你不应该担心性能。这种微小的优化想法是可怕的。除非你正在讨论在长时间运行的for循环的每次迭代中抛出异常,否则你很可能不会因为异常使用方式而遇到性能问题。

当你有指标表明你需要优化性能时,总是要优化性能,然后着手处理被证明是罪魁祸首的地方。

有易于调试的可读代码(即不隐藏堆栈跟踪)比让代码运行得更快好得多。

最后关于将异常包装成自定义异常...这可以是一个非常有用的结构,特别是在处理UI时。您可以将每个已知和合理的异常情况都包装在某个基本自定义异常中(或者将其扩展自基本异常),然后UI只需捕获此基本异常。当捕获异常时,异常需要提供显示信息给用户的手段,比如ReadableMessage属性等等。因此,每当UI错过一个异常时,这是因为您需要修复的错误,而每当它捕获一个异常时,则是已知的错误条件,可以并且应该由UI正确处理。


2
显然,创建新对象(即新的异常)会导致惩罚,因此,正如您对程序的每一行代码所做的一样,您必须决定更好的异常分类是否值得额外的工作。作为做出决策的建议,如果您的新对象没有携带有关异常的额外信息,则可以忘记构造新异常。但是,在其他情况下,拥有异常层次结构对于您类的用户非常方便。假设您正在实现Facade模式,那么迄今考虑的场景都不好:
1.将每个异常作为异常对象引发并不好,因为您可能会丢失有价值的信息。 2.也不好捕获每种对象,因为这样做会导致Facade创建失败。
在这种假设情况下,最好的做法是创建一组异常类的层次结构,该层次结构使用户摆脱了系统内部复杂性,并允许他们了解产生的异常类型。另外需要注意的是:我个人不喜欢使用异常(从Exception类派生的类的层次结构)来实现逻辑,例如:
try {
        // something that will raise an exception almost half the time
} catch( InsufficientFunds e) {
        // Inform the customer is broke
} catch( UnknownAccount e ) {
        // Ask for a new account number
}

2

不要这样做:

try
{
    // some code
}
catch (Exception ex) { throw ex; }

由于这样会丢失堆栈跟踪信息。

相反,应该这样做:

try
{
    // some code
}
catch (Exception ex) { throw; }

只需抛出异常即可,如果您希望将其作为新自定义异常的内部异常传递异常变量。


1
小改动 - 去掉 ex,否则会创建未使用变量警告。尝试 { // 一些代码 } 捕获 (Exception) { 抛出; } - Dennis

2

像David一样,我认为第二个和第三个表现更好。但是这三个中是否有一个表现不佳以至于要花时间担心它?我认为有比性能更大的问题需要担心。

FxCop总是建议使用第三种方法,以避免丢失原始堆栈跟踪。

编辑:删除了一些明显错误的内容,Mike很好心地指出了这一点。


1

正如其他人所说,最佳性能来自于底部的那个,因为你只是重新抛出一个现有的对象。中间的那个是最不正确的,因为它丢失了堆栈。

如果我想在代码中解耦某些依赖关系,我个人会使用自定义异常。例如,我有一个从XML文件加载数据的方法。这可能会以许多不同的方式出错。

它可能无法从磁盘读取(FileIOException),用户可能尝试从他们不允许的地方访问它(SecurityException),文件可能损坏(XmlParseException),数据可能格式错误(DeserialisationException)。

在这种情况下,为了让调用类更容易理解所有这些,所有这些异常都会重新抛出单个自定义异常(FileOperationException),这意味着调用者不需要引用System.IO或System.Xml,但仍然可以通过枚举和任何重要信息访问发生的错误。

如上所述,不要试图微观优化这样的东西,抛出异常本身就是这里发生的最慢的事情。最好的改进是尝试避免异常。

public bool Load(string filepath)
{
  if (File.Exists(filepath)) //Avoid throwing by checking state
  {
    //Wrap anyways in case something changes between check and operation
    try { .... }
    catch (IOException ioFault) { .... }
    catch (OtherException otherFault) { .... }
    return true; //Inform caller of success
  }
  else { return false; } //Inform caller of failure due to state
}

0

等一下.... 如果抛出异常,我们为什么要关心性能呢?除非我们将异常作为正常应用程序流程的一部分使用(这远远违反了最佳实践)。

我只看到过与成功相关的性能要求,而从未看到与失败相关的性能要求。


0

在您的第一个示例中,throw 会产生一个新的 CustomException 对象的开销。

在您的第二个示例中,re-throw 将抛出 Exception 类型的异常。

在您的第三个示例中,re-throw 将抛出与“某些代码”引发的相同类型的异常。

因此,第二个和第三个示例使用更少的资源。


0

从纯性能角度来看,我猜第三种情况是最有效的。其他两种需要提取堆栈跟踪并构造新对象,这两个过程都可能非常耗时。

话虽如此,这三个代码块具有非常不同的(外部)行为,因此将它们进行比较就像在问快速排序是否比将项目添加到红黑树更有效一样。选择正确的事情比这更重要。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接