The using
block gets turned by the compiler into a try
/finally
block of its own, within the existing try
block.
For example:
try
{
using (MemoryStream ms = new MemoryStream())
throw new Exception();
}
catch (Exception)
{
throw;
}
becomes
.try
{
IL_0000: newobj instance void [mscorlib]System.IO.MemoryStream::.ctor()
IL_0005: stloc.0
.try
{
IL_0006: newobj instance void [mscorlib]System.Exception::.ctor()
IL_000b: throw
} // end .try
finally
{
IL_000c: ldloc.0
IL_000d: brfalse.s IL_0015
IL_000f: ldloc.0
IL_0010: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0015: endfinally
} // end handler
} // end .try
catch [mscorlib]System.Exception
{
IL_0016: pop
IL_0017: rethrow
} // end handler
The compiler won't rearrange things. So it happens like this:
- Exception is thrown in, or propagates to, the
using
block's try
part
- Control leaves the
using
block's try
part, and enters its finally
part
- Object is disposed by the code in the
finally
block
- Control leaves the finally block, and the exception propagates out to the outer
try
- Control leaves the outer
try
and goes into the exception handler
Point being, the inner finally
block always runs before the outer catch
, because the exception doesn't propagate til the finally
block finishes.
The only normal case where this won't happen, is in a generator (excuse me, "iterator").
An iterator gets turned into a semi-complicated state machine, and finally
blocks are not guaranteed to run if it becomes unreachable after a yield return
(but before it has been disposed).
catch (Exception ex) where ex:ThisException, ThatException
[obvious meaning] orfinally (Exception ex)
[would behave as afinally
block, but withex
being set to the exception, if any, that will be thrown when thefinally
block completes, ornull
if none; useful e.g. in factory functions that returnIDisposable
]. – Asphalt