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

Leo*_*Hat 1 c# resources exception

我正在使用一些代码,它们使用异常来处理某些错误条件(当然不是最好的设计,但它正是我正在使用的).

当代码中发生异常时,我需要一种优雅的方法来清理任何开放或临时资源.

这可以这样执行:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

假设该foo()方法在自身之后正确清理,但该bar()方法引发了异常.在清理代码中,我们将cleanupResourceFoo() 首先调用它本身将抛出异常,因为foo资源已经被清理.

这意味着cleanupResourceBar()最终不会被调用,我们最终会导致资源泄漏.

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

try
{
    cleanupResourceFoo();
}
catch
{
}
try
{
    cleanupResourceBar();
}
catch
{
}
Run Code Online (Sandbox Code Playgroud)

但现在我们变得非常难看.

我来自C++背景,这是我通常使用RAII的东西.有关在C#中处理此问题的优雅方法的任何建议?

Jon*_*eet 6

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

using (FirstResource r1 = ...)
{
    using (SecondResource r2 = ...)
    {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你只是想清理资源上的异常,这是比较少见-而不是我所期待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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

通过不捕获异常,这允许错误传播而不是被吞噬.这通常是你想要的 - 如果不是你的情况,或许你可以更准确地解释你的情况.

你已经说过,你有效地想要忽略非致命的异常 - 但是你通常不应该只抓住Exception并继续前进:捕捉你在特定情况下所期望的特定异常.显然你可能有一个非常特殊的情况,但这是大多数时候都持有的一般建议.如果你真的想要,你可以重构上面的帮助方法以捕获异常.