首页 / 知识

关于.net:引发异常的性能注意事项

2023-04-11 22:58:00

关于.net:引发异常的性能注意事项

Performance Considerations for throwing Exceptions

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

1
2
3
4
5
6
7
8
9
try
{
    ... // some code
}
catch (Exception ex)
{
    ... // Do something
    throw new CustomException(ex);
}

基本上,编码器正在做的是将它们包含在自定义异常中并再次抛出该异常。

这与以下两个方面在性能方面有何不同:

1
2
3
4
5
6
7
8
9
try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw ex;
}

要么

1
2
3
4
5
6
7
8
9
try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw;
}

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


@布拉德·图特洛

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

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

当您拥有需要优化性能的指标时,请务必优化性能,然后找到被证明是罪魁祸首的位置。

拥有易于调试的功能(例如,IE不隐藏堆栈跟踪)的可读代码比使某些程序运行速度快一纳秒要好得多。

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


不要做:

1
2
3
4
5
try
{
    // some code
}
catch (Exception ex) { throw ex; }

因为这将丢失堆栈跟踪。

而是:

1
2
3
4
5
try
{
    // some code
}
catch (Exception ex) { throw; }

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


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

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

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

  • 将所有异常都作为Exception对象引发是不好的,因为您丢失了(可能)有价值的信息
  • 也不善举捕获的每种对象,因为这样做会导致创建外观失败
  • 在这种假设的情况下,更好的方法是创建异常类的层次结构,该类将用户从系统的内部复杂性中抽象出来,使他们对所产生的异常类型有所了解。

    附带说明:

    我个人不喜欢使用异常(从Exception类派生的类的层次结构)来实现逻辑。像这样:

    1
    2
    3
    4
    5
    6
    7
    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
    }

    我想像大卫一样,第二和第三名的表现更好。但是,这三者中的哪一个表现不佳,足以花时间担心它吗?我认为存在比性能更值得担心的问题。

    FxCop始终建议第三种方法优于第二种方法,以免丢失原始堆栈跟踪。

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


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

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

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

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

    如前所述,不要试图对这样的事情进行微优化,根本不会发生抛出异常的行为。最好的改进就是尝试完全避免异常。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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
    }

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

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


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

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


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

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

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

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


    性能类型是一个这是

    最新内容

    相关内容

    热门文章

    推荐文章

    标签云

    猜你喜欢