【问题标题】:Are these try/catch'es equivalent?这些 try/catch'es 是否等效?
【发布时间】:2017-06-11 21:52:22
【问题描述】:

场景

我有一个执行数据库操作的方法(比方说)。如果在该操作期间引发任何异常,我只想将该异常抛出给调用者。我不想在 catch 块中执行任何特定任务,假设调用者将对该异常执行它想做的任何事情。在这种情况下,哪一种是合适的异常处理技术?

try
{
    // Some work that may generate exception
}
catch(Exception)
{
    throw;
}
finally
{
    // Some final work
}

上面的等价于下面的try/catch/finally吗?

try
{
    // Some work that may generate exception
}
catch
{
    throw;
}
finally
{
    // Some final work
}

上面的等价于下面的try/finally吗?

try
{
    // Some work that may generate exception
}
finally
{
    // Some final work
}

哪一个比另一个好?应该使用哪一个?

【问题讨论】:

  • 问题帖子最底部的问题很难回答,因为这3个我只能推荐底部的一个。一般来说,不建议捕获 all 异常然后简单地重新抛出它们。我假设您打算在 catch 块内处理它们,但在这种情况下,这取决于您打算做什么。如果您打算记录异常并简单地重新抛出,那么是的,捕获任何类型的异常可能有效,但在几乎所有其他情况下,您应该只捕获您知道如何捕获的异常处理。
  • 此外,前 2 个与最后一个不同之处在于您没有 捕获块,但如果该捕获块只是重新抛出异常无论如何(就像你的例子一样),并且没有额外/其他处理,那么完全有 catch 块似乎是可疑的。
  • 基本上,建议做什么取决于您的问题中没有的上下文。
  • @HimBromBeere 如果您问“哪一个比其他的更好”,如果有客观的方法来衡量“好”,则不必必须基于意见每个备选方案的。我的观点是,这个问题没有足够的背景来推荐任何东西,除了提出更具体的场景,很可能存在好的和可靠的建议。
  • 新场景位于顶部,如果您不打算做任何事情,除了任何例外,您什么都不需要。异常将在没有您帮助的情况下传播到调用堆栈(在某些情况下跨越线程和任务边界,具体取决于所涉及的框架)。只有当您打算处理需要使用try/catch 的异常时。如果您想确保某些清理代码始终运行,则可能需要 try/finally。换句话说,对于您的特定场景,如果需要,请使用 try/finally 替代方案,或者根本不使用 try/catch/finally。

标签: c# .net exception exception-handling


【解决方案1】:

不,它们不相等。它们可能在某些情况下是等价的,但一般答案是否定的。

不同种类的catch

catch 具有指定异常类型的块

以下将仅捕获从System.Exception 继承的托管异常,然后执行finally 块,无论是否引发异常,都会发生这种情况。

try
{
   // Some work that may generate exception
}
catch (Exception)
{
   throw;
}
finally
{
   // Some final work
}

catch 没有指定异常类型的块

下面不带类型说明符的catch块也会捕获非托管异常,这些异常不一定由托管System.Exception对象表示,然后执行finally块,无论是否异常都会发生是否被抛出。

try
{
   // Some work that may generate exception
}
catch
{
   throw;
}
finally
{
   // Some final work
}

没有catch 块的finally

如果您根本没有catch 块,则无论是否发生异常,您的finally 仍将被执行。

try
{
   // Some work that may generate exception
}
finally
{
  // Some final work
}

它们何时等效?

如果您的catch 块没有指定异常并且只包含throw; 语句,那么最后两个确实是等价的。如果您不关心非托管异常并且您的 catch 块仅包含 throw; 语句,则这三个都可以视为等效。

注意事项

关于throw的说明

以下两段代码包含细微差别。后者将重新抛出异常,这意味着它将重写异常的堆栈跟踪,因此这些绝对等效:

catch (Exception e)
{
    throw;
}

catch (Exception e)
{
    throw e;
}

如果您将finallyIDisposable 一起使用,则以下两段代码几乎是等价的,但有一些细微差别:

  • 当对象为空时,using 语句不会给你一个NullReferenceException
  • 当使用try-finally 技术时,变量仍保留在作用域内,尽管非常不鼓励在处置后使用任何对象。但是,您仍然可以将变量重新分配给其他东西。

    东西 obj = null; 尝试 { obj = 新的东西() // 做一点事 } 最后 { obj.Dispose(); }

using (var obj = new Something())
{
    // Do something
}

【讨论】:

  • 我明白了,谢谢。还有一个问题。当抛出异常时,如 throw e;它将写入异常的堆栈跟踪。这是否会增加对应用程序的额外性能影响。
  • @Bhuban 您没有理由要这样做。要么你想重新抛出,要么你想抛出另一个异常(可能原始异常为InnerException)。当然,所有需要做的事情都会对性能产生影响——只有您才能判断性能影响在您的应用程序中是否明显,并且只能通过测量来判断。一般来说,异常是exceptional,所以在异常情况下昂贵的代码通常是好的 - 如果你发现抛出异常会使你的程序太慢,你应该修复潜在的问题。
  • 请注意,无论如何,1 和 2大部分是等效的,因为非托管异常被运行时包装到托管异常,例如 SEHException。从技术上讲,catch { ... } 编译成类似catch (object) { ... } 的东西 - CLI 通过 CLS 要求将 System.Exception 作为异常的基类来支持其他对象类型的传播。
  • 重新。 using 解释:不同之处在于 obj 在 finally 之后仍然在范围内,这可能是相关的。
  • 关于using 语句的注释,编译器处理objfinally 块中为null 的情况。
【解决方案2】:

这两个try / catch 语句是等效的,因为它们重新抛出了捕获的原始异常。然而,空的 catch 更广泛(正如 Venemo 已经说过的,捕获非托管异常)。如果您要 catch 一个异常并将其捕获在一个变量中,您可以将其用于日志记录,或者您可以在将原始异常作为参数传递时 throw 一个新异常 - 使其成为 "inner exception"

finally 无论如何都会发挥同样的作用。

应该使用哪一个,在我们不需要记录异常并且我们明确假设调用者将处理引发的异常(例如写入文件流或发送电子邮件)的情况下。

如果调用者将处理异常并且您不需要在此级别记录异常的发生,那么您根本不应该捕获。如果调用者将处理抛出的异常,则无需捕获异常只是为了重新抛出它。

捕获将被重新抛出的异常的正当理由:

  • throw new Exception("WTF Happened", ex); // Use as inner exception
  • 日志异常
  • 使用finally 块执行一些清理代码

【讨论】:

    【解决方案3】:

    到目前为止,您已经有了一些很好的答案,但是到目前为止他们没有提到一个有趣的区别。考虑:

    try { ImpersonateAdmin(); DoWork(); } 
    finally { RevertImpersonation(); }
    

    try { ImpersonateAdmin(); DoWork(); }
    catch { RevertImpersonation(); throw; }
    finally { RevertImpersonation(); }
    

    假设 DoWork 抛出。

    现在手头的第一个问题是“是否有可以处理此异常的 catch 块”,因为如果答案为“否”,则 程序的行为是实现定义的。运行时可能会选择立即终止进程,它可能会选择在终止进程之前运行 finally 块,它可能会选择在未处理的异常点启动调试器,它可能会选择做任何它喜欢的事情。带有未处理异常的程序可以做任何事情。

    所以运行时开始寻找一个 catch 块。此 try 语句中没有任何内容,因此它查找调用堆栈。假设它找到一个带有异常过滤器的。它需要知道过滤器是否允许处理异常,因此过滤器在恢复模拟之前运行。如果过滤器意外或故意做了只有管理员才能做的事情,它就会成功!这可能不是你想要的。

    在第二个示例中,catch 立即捕获异常,恢复模拟,抛出,现在运行时开始寻找一个 catch 块来处理重新抛出。现在,如果有一个过滤器,它会在 之后运行,模拟被还原。 (当然 finally then 会再次恢复;我假设恢复模拟在这里是幂等的。)

    这是这两个代码sn-ps的重要区别。如果绝对肯定禁止任何代码看到被try弄乱并被finally清理的全局状态,那么你必须在finally之前捕获。 “终于”不是“马上”的意思,而是“最终”的意思。

    【讨论】:

      猜你喜欢
      • 2020-02-01
      • 2023-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-11
      • 2012-11-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多