在可能抛出异常时清理多个资源的优雅方式
本文关键字:资源 方式 抛出异常 | 更新日期: 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方法来捕获异常。