在C#中,一个被通配符catch块所跟随的finally块有什么用处?

11

考虑以下C#代码结构(S0-S3是任意代码块的占位符):

try
{
    S0;
}
catch (Exception ex)
{
    S1;
}
finally
{
    S2;
}

S3;

如果S1在catch处理程序内部抛出异常,那么finally中的S2仍将执行(但S3不会执行)。

问题

假设S1不能抛出异常,在finally块中放置S2而不是将其放置在try/catch/finally之外,在S3之前是否有意义?

示例

try
{
    // Do something that might throw
}
catch (Exception ex)
{
    // Save the exception to re-throw later
    // NB: This statement cannot throw an exception!
    this.cachedException = ex;
}
finally
{
    S2;
}

S3;

finally块有什么意义吗?如果在严格假设catch块内部不会抛出异常的情况下,以下代码不是等价的吗:

try
{
    // Do something that might throw
}
catch (Exception ex)
{
    // Save the exception to re-throw later
    // NB: This statement cannot throw an exception!
    this.cachedException = ex;
}

// No finally block needed (?)
S2;
S3;

次要问题

更新:如果接受上述两个代码块在所声明的假设下是等效的,那么考虑到答案中有关代码清晰度的反馈,将S2和S3组合在finally块内是否更好(且等效)?

try
{
    // Do something that might throw
}
catch (Exception ex)
{
    // Save the exception to re-throw later
    // NB: This statement cannot throw an exception!
    this.cachedException = ex;
}
finally
{
    S2; // Put S2 and S3 together inside the `finally` block to guard against
    S3; // future changes in the `catch` filter, or handling code.
}
10个回答

12

假定S1不能抛出异常是一个脆弱的假设,考虑到资源枯竭情况(例如内存用尽)。即使这个假设成立(这是一个大的假设),对代码进行微小的更改也可能引入异常。

由于S2通常涉及清理和释放有价值的资源,将其放在finally块中可以明确地传达这个意图。在可能的情况下,将这样的代码放在资源拥有对象的Dispose()方法中,并将try/finally子句替换为using子句,可以更好地传达意图(对于C#来说更符合惯用法)。

每当你可以用两种或更多种不同的方式编写某些代码时,请使用最清晰、最稳定且更不容易受到更改影响的那种方式。

关于次要问题:如果S3涉及清理工作,则应将其放置在finally块中。如果它假定try块成功,则应将其放置在finally块之后。如果catch语句不重新抛出异常,我个人会解释为已经成功,并可以继续正常操作。但是,“保存异常以稍后重新抛出”这件事让我感到困惑。通常情况下,我建议不要将异常存储以便在方法外重新抛出异常。这是不寻常的,对我来说似乎很困惑。你的代码包含的越少意外,维护就会更容易(包括三个月后的自己)。


Pontus:我同意你对保存异常的看法。这很不寻常。这是由于实现异步编程模型的代码正在工作线程上执行代码 - 它需要保存任何异常以供稍后使用,并在调用EndXxx时重新抛出它。 - Daniel Fortunov

10

有一种情况您可能没有预料到S1会抛出异常:如果线程被中断,异常将自动在catch块结束时重新抛出。

正如Pontus所说,finally 块表示这段代码无论发生什么都应该始终运行。这比捕获所有异常然后继续执行更清晰明了。


一个线程被中断是什么意思?它会在什么时候发生? - Arve
@Arve:如果有任何东西为该线程调用Thread.Interrupt...请参阅文档以获取详细信息。 - Jon Skeet
哦,是的,那个老掉牙的问题。我一时忘记了异步异常! - Daniel Fortunov

3
我认为这类似于在if语句内容周围加上括号。
虽然你可以说
if (x) y;

不会失败,并不意味着一些经验较少的程序员以后不会对其进行编辑。
if (x) y; z;

也许在你的代码中,finally块并不是必需的,但是将其保留总是一个好主意,以防catch块中的代码发生变化。

if (x) 
{
    y;
}

对我来说,始终胜出的是它。


3

finally块用于资源清理,无论是否发生异常,finally块都会被调用,因此它是执行清理工作的理想位置。


3
请注意,即使try或catch块包含return语句,finally子句也会被执行。

+1,我正准备自己添加这个答案。在我看来,这应该是最佳答案之一。 - Connell

2

在您的情况下,没有区别。

但是,如果您没有捕获Exception,而是捕获了某个特定的Exception子类,则会有所不同。这可能是查看try-catch-finally块时的正常模式。


1
在第二种情况下,只有当你吞咽了 catch 块中捕获的任何异常时,控制流才会确保到达 S2!在这里和那里随意使用 Try-Catch 是不应该的,需要小心谨慎。
例如:
try
{
    // Do something that might throw
}
catch (Exception ex)
{
    // Save the exception to re-throw later
    // NB: This statement cannot throw an exception!
    // this.cachedException = ex;

    // Must swallow any exception here to let control go further!

    // If you're not sure enough [which you and me both are not likely to be] - use finally to execute S2 in any condition
}

// No finally block needed (?)
S2;
S3;

或者如果没有抛出异常。 - Hogan

1

你现在可能能够确保你的代码不会抛出异常,但是当你在6个月后回来看它时呢?或者其他人需要进行更改呢?

使用finally块是一种被广泛认可的模式,任何程序员都能理解,如果你的代码做了一些特殊和不同的事情,它将会让你或其他人遇到麻烦。


1
为了确保在异常发生时清除资源,使用try/finally代码块,并在finally子句中关闭资源。使用try/finally代码块可确保即使发生异常,资源也会被处理...有关更多详细信息,请参见http://itpian.com/Coding/5143-Need-for-Finally-block.aspx

0

不,你不必使用它。在那种情况下不是必需的。这是你的选择,取决于你可能使用/不使用的资源。如果你需要清理任何资源,那么finally块是最好的选择。


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