I'd like to provide a class to manage creation and subsequent deletion of a temporary directory. Ideally, I'd like it to be usable in a using block to ensure that the directory gets deleted again regardless of how we leave the block:
static void DoSomethingThatNeedsATemporaryDirectory()
{
using (var tempDir = new TemporaryDirectory())
{
// Use the directory here...
File.WriteAllText(Path.Combine(tempDir.Path, "example.txt"), "foo\nbar\nbaz\n");
// ...
if (SomeCondition)
{
return;
}
if (SomethingIsWrong)
{
throw new Exception("This is an example of something going wrong.");
}
}
// Regardless of whether we leave the using block via the return,
// by throwing and exception or just normally dropping out the end,
// the directory gets deleted by TemporaryDirectory.Dispose.
}
Creating the directory is no problem. The problem is how to write the Dispose method. When we try to delete the directory, we might fail; for example because we still have a file open in it. However, if we allow the exception to propagate, it might mask an exception that occurred inside the using block. In particular, if an exception occurred inside the using block, it might be one that caused us to be unable to delete the directory, but if we mask it we have lost the most useful information for fixing the problem.
It seems we have four options:
- Catch and swallow any exception when trying to delete the directory. We might be left unaware that we're failing to clean up our temporary directory.
- Somehow detect if the Dispose is running as part of stack unwinding when an exception was thrown and if so either suppress the IOException or throw an exception that amalgamates the IOException and whatever other exception was thrown. Might not even be possible. (I thought of this in part because it would be possible with Python's context managers, which are in many ways similar to .NET's IDisposable used with C#'s using statement.)
- Never suppress the IOException from failing to delete the directory. If an exception was thrown inside the using block we will hide it, despite there being a good chance that it had more diagnostic value than our IOException.
- Give up on deleting the directory in the Dispose method. Users of the class must remain responsible for requesting deletion of the directory. This seems unsatisfactory as a large part of the motivation for creating the class was to lessen the burden of managing this resource. Maybe there's another way to provide this functionality without making it very easy to screw up?
Is one of these options clearly best? Is there a better way to provide this functionality in a user-friendly API?
finally
block is running because of an exception (much less what it is). I would posit that a substantial plurality if not a majority ofcatch
blocks should either befault
blocks or conditional code infinally
; code should only catch an exception if it has a reasonable expectation of resolving it, or will actively wrap it, as opposed to simply doing a blankthrow
. Prime example: an exception within anIDisposable
object's constructor should clean up the object, but should not be "caught". – Dwt