引发异常的性能注意事项

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

我已经多次遇到以下类型的代码,并且我想知道这是否是一个好习惯(从性能角度来看):

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

除了功能或者编码方面的最佳实践参数外,这三种方法之间是否存在性能差异?

解决方案

回答

第一个示例中的抛出操作具有创建新的CustomException对象的开销。

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

第三个示例中的重新引发将引发与"某些代码"引发的类型相同的异常。

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

回答

我想像大卫一样,第二和第三名的表现更好。但是,这三个公司中的任何一个是否会表现得很差,足以花时间担心它?我认为存在比性能更值得担心的问题。

FxCop始终建议使用第三种方法,而不是第二种,以免丢失原始堆栈跟踪。

编辑:删除了完全是错误的内容,而Mike足够指出。

回答

显然,我们要承担创建新对象的代价(新的Exception),因此,就像我们对程序中添加的每一行代码所做的一样,我们必须确定对异常进行更好的分类是否可以支付额外的工作。

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

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

  • 将所有异常都作为Exception对象引发是不好的,因为我们丢失了(可能)有价值的信息
  • 也不善举捕获的所有对象,因为这样做会导致创建立面失败

在这种假设的情况下,最好的做法是创建异常类的层次结构,该类将用户从系统的内部复杂性中抽象出来,使他们对所产生的异常类型有所了解。

附带说明:

我个人不喜欢使用异常(从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
}

回答

@布拉德·图特洛

在第一种情况下,异常不会丢失,而是将异常传递给构造函数。我会在其余方面与我们保持一致,第二种方法是一个非常糟糕的主意,因为会丢失堆栈跟踪。当我使用.NET时,我遇到了很多其他程序员这样做的情况,当我需要查看异常的真正原因,却发现它从一个巨大的try块中被抛出时,它使我无休止地无休止地工作。我现在不知道问题的根源。

我还赞同布拉德的评论,即我们不必担心性能。这种微优化是一个可怕的想法。除非我们要讨论的是长时间运行的for循环的每个迭代中都引发异常,否则我们很可能不会因使用异常而遇到性能问题。

当具有指示需要优化性能的指标时,请始终优化性能,然后找到被证明是罪魁祸首的位置。

具有易于调试功能(即,IE不隐藏堆栈跟踪)的可读代码比使内容运行速度快一纳秒要好得多。

关于将异常包装到自定义异常中的最后说明...这可能是一个非常有用的构造,尤其是在处理UI时。我们可以将所有已知且合理的特殊情况包装到某个基本自定义异常(或者从所述基本异常扩展而来的异常)中,然后UI可以捕获此基本异常。捕获到异常后,将需要提供一种向用户显示信息的方法,例如ReadableMessage属性或者类似内容。因此,每当UI遗漏异常时,这都是由于我们需要修复的错误所致,并且每当它捕获异常时,UI都可以并且应该适当地处理它是已知的错误情况。

回答

从纯粹的性能角度来看,我猜第三种情况表现最好。另外两个需要提取堆栈跟踪并构造新的对象,这两个对象都可能非常耗时。

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

回答

正如其他人所说,最好的性能来自最底层,因为我们只是在扔掉一个现有的对象。中间的那个最不正确,因为它会使堆栈松动。

如果我想分离代码中的某些依赖项,我个人使用自定义异常。例如,我有一个从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
}

回答

不要做:

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

因为这将丢失堆栈跟踪。

相反,请执行以下操作:

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

只是throw会做,如果我们希望将其作为新的自定义异常的内部异常,则只需要传递异常变量即可。

回答

等待....如果引发异常,为什么我们还要关心性能?除非我们将异常用作常规应用程序流程的一部分(最好的做法是WAYYYY)。

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