Elegant way of cleaning up multiple resources when exceptions may be thrown
I'm working in some code that uses exceptions a lot to handle certain error conditions (certainly not the best design by any means, but it's what I'm working with).
When an exception occurs in code, I need an elegant way of cleaning up any open or temporary resources.
This can be performed like this:
try
{
foo();
bar();
}
catch (Exception)
{
// Oops, an error occurred - let's clean up resources
// An开发者_StackOverflowy 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
}
}
Let's say that the foo()
method cleaned up after itself properly, but the bar()
method threw an exception. In the cleanup code, we will call cleanupResourceFoo()
first which itself will throw an exception as the foo
resources have already been cleaned up.
This means that cleanupResourceBar()
won't end up being called and we'll end up with a resource leak.
Of course we could re-write the inner try/catch
block like so:
try
{
cleanupResourceFoo();
}
catch
{
}
try
{
cleanupResourceBar();
}
catch
{
}
but now we're getting pretty ugly.
I come from a C++ background and this is the kind of thing I would normally use RAII for. Any recommendations about an elegant way to handle this in C#?
Cleaning up resources should almost always be handled via using
statements and IDisposable
- so you'd simply have:
using (FirstResource r1 = ...)
{
using (SecondResource r2 = ...)
{
...
}
}
If you only want to clean up resources on exception, that's somewhat rarer - and not something I'd expect RAII to particularly help you with in C++. You could potentially use delegates to make this simpler:
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();
}
}
}
By not catching the exception, this allows errors to propagate rather than being swallowed. This is normally what you want - if it isn't in your case, perhaps you could explain more precisely what your situation is.
You've said that there are non-fatal exceptions that you effectively want to ignore - but you generally shouldn't just catch Exception
and keep going: catch specific exceptions that you expect in particular situations. Obviously you may have a very special situation, but this is general advice which holds most of the time. You can restructure the helper method above to catch exception if you really want.
精彩评论