抛出异常的性能注意事项

时间:2008-08-09 20:01:09

标签: .net exception optimization performance

我多次遇到过以下类型的代码,我想知道这是不是一个好的做法(从性能角度来看):

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;
}

不考虑任何功能或编码最佳实践论点,这3种方法之间是否有任何性能差异?

8 个答案:

答案 0 :(得分:10)

@Brad Tutterow

在第一种情况下,异常没有丢失,它被传递给构造函数。我会同意你的意见,第二种方法是一个非常糟糕的主意,因为堆栈跟踪的丢失。当我使用.NET时,我遇到了许多其他程序员就是这样做的情况,当我需要查看异常的真正原因时,它让我感到沮丧,只是发现它是从一个巨大的try块重新抛出的。我现在不知道问题出在哪里。

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

如果您的指标表明您需要优化效果,那么请始终优化效果,然后点击被证明是罪魁祸首的地方。

使用具有简单调试功能的可读代码(IE不隐藏堆栈跟踪)而不是使某些内容运行速度快一纳秒更好。

关于将异常包装到自定义异常中的最后一个注释...这可能是一个非常有用的构造,尤其是在处理UI时。您可以将每个已知且合理的异常情况包装到一些基本自定义异常(或从所述基本异常扩展的异常)中,然后UI可以捕获此基本异常。捕获时,异常将需要提供向用户显示信息的方法,比如ReadableMessage属性,或者沿着这些行显示的内容。因此,只要UI错过了一个异常,就是因为你需要修复一个bug,并且只要它捕获异常,它就是一个已知的错误条件,可以并且应该由UI正确处理。

答案 1 :(得分:2)

显然,您会因创建新对象(新的Exception)而受到惩罚,因此,正如您对附加到程序的每一行代码所做的那样,您必须确定更好的异常分类是否支付额外费用工作。

作为做出该决定的建议,如果您的新对象没有携带有关异常的额外信息,那么您可以忘记构建新的异常。

但是,在其他情况下,具有异常层次结构对于类的用户来说非常方便。假设你正在实现Facade模式,到目前为止所考虑的方案都不好:

  1. 将每个异常作为异常对象引发并不好,因为您丢失了(可能)有价值的信息
  2. 既不能提高您捕获的各种物体也不好,因为这样做会导致您无法创建立面
  3. 在这个假设的情况下,最好的做法是创建一个异常类层次结构,从系统的内部复杂性中抽象出用户,使他们能够了解产生的异常类型。

    作为旁注:

    我个人不喜欢使用异常(从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 :(得分:2)

不要这样做:

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

因为这会丢失堆栈跟踪。

取而代之的是:

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

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

答案 3 :(得分:2)

像大卫一样,我认为第二和第三的表现更好。但三个人中的任何一个表现得都不够好,可以随时担心吗?我认为担心的问题比性能要大。

FxCop总是建议第二种方法超过秒,以便原始堆栈跟踪不会丢失。

编辑:删除了一些完全错误的内容,Mike非常友好地指出。

答案 4 :(得分: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
}

答案 5 :(得分:0)

从纯粹的表演角度来看,我猜第三种情况是最有效的。另外两个需要提取堆栈跟踪并构造新对象,这两个对象都可能相当耗时。

说过这三个代码块有非常不同(外部)行为,因此比较它们就像询问QuickSort是否比将项目添加到红黑树更有效。这并不像选择正确的事情那么重要。

答案 6 :(得分:0)

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

我只看到了成功方面的性能要求,但从未见过失败。

答案 7 :(得分:0)

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

第二个示例中的重新抛出将抛出异常类型的异常。

第三个示例中的重新抛出将抛出“某些代码”抛出的相同类型的异常。

所以第二个和第三个例子使用的资源更少。