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).