在可能抛出异常时清理多个资源的优雅方式

本文关键字:资源 方式 抛出异常 | 更新日期: 2023-09-27 18:08:45

我正在编写一些代码,这些代码大量使用异常来处理某些错误条件(无论如何肯定不是最好的设计,但这是我正在使用的)。

当代码中出现异常时,我需要一种优雅的方式来清理任何打开的或临时的资源。

可以这样执行:

try
{
    foo();
    bar();
}
catch (Exception)
{
    // Oops, an error occurred - let's clean up resources
    // Any attempt to cleanup non-existent resources will throw
    // an exception, so let's wrap this in another try block
    try
    {
        cleanupResourceFoo();
        cleanupResourceBar();
    }
    catch
    {
        // A resource didn't exist - this is non-fatal so let's drop
        // this exception
    }
}

假设foo()方法正确地清理了自己,但是bar()方法抛出了一个异常。在清理代码中,我们将首先调用cleanupResourceFoo() ,这将抛出一个异常,因为foo资源已经被清理了。

这意味着cleanupResourceBar()最终不会被调用,我们将以资源泄漏结束。

当然,我们可以像这样重写内部try/catch块:

try
{
    cleanupResourceFoo();
}
catch
{
}
try
{
    cleanupResourceBar();
}
catch
{
}

但是现在我们变得很难看。

我来自c++背景,这是我通常使用RAII的那种事情。有什么好的方法可以在c#中处理这个问题吗?

在可能抛出异常时清理多个资源的优雅方式

清理资源应该几乎总是通过using语句和IDisposable来处理-所以您只需:

using (FirstResource r1 = ...)
{
    using (SecondResource r2 = ...)
    {
        ...
    }
}
如果你只想要在异常时清理资源,那就比较少见了——我不希望RAII在c++中特别帮助你。您可以使用委托来简化此操作:
TryWithCleanUpOnException(foo, cleanUpResourceFoo);
TryWithCleanUpOnException(bar, cleanUpResourceBar);
...
private static void TryWithCleanUpOnException(Action action,
                                              Action cleanUp)
{
    bool success = false;
    try
    {
        action();
        success = true;
    }
    finally
    {
        if (!success)
        {
            cleanup();
        }
    }
}
通过不捕获异常,这允许错误传播而不是被吞下。这通常是你想要的——如果不是你的情况,也许你可以更准确地解释一下你的情况。

你已经说过,有一些非致命的异常是你想要忽略的——但是你通常不应该只捕获Exception然后继续:捕获特定的异常,你期望在特定的情况下。显然,你可能有一个非常特殊的情况,但这是一个普遍的建议,适用于大多数时候。如果需要的话,可以重构上面的helper方法来捕获异常。