.net under the covers — using/Using/use
Something I had occasion to look at the other day was exactly how the auto-disposal mechanism actually works for the out of the box .net languages: and there are some interesting quirks to be found. Take the equivalent sample code fragments
using (var r = new StringReader(text)) | |
{ | |
Console.WriteLine(r.ReadToEnd()); | |
} |
Using r As New StringReader(text) | |
Console.WriteLine(r.ReadToEnd()) | |
End Using |
use r = new StringReader(text) | |
printfn "%A" <| r.ReadToEnd() |
and for completeness, C++/CLI stack based disposal
{ | |
StringReader r(text); | |
Console::WriteLine(r.ReadToEnd()); | |
} |
All languages and configurations render this as a try {} finally { Dispose() }
construction (apart from stack-based C++/CLI semantics, which generates a try {} catch { Dispose(); throw;} Dispose()
), but there are devils in the details. In release mode, C# and VB compile to the same IL -- just dispose if the used value is not null
finally | |
{ | |
IL_001a: ldloc.1 | |
IL_001b: brfalse.s IL_0023 | |
IL_001d: ldloc.1 | |
IL_001e: callvirt instance void [mscorlib]System.IDisposable::Dispose() | |
IL_0023: endfinally | |
} // end handler |
whereas F# compiles to
finally | |
{ | |
IL_002c: ldloc.0 | |
IL_002d: isinst [mscorlib]System.IDisposable | |
IL_0032: stloc.3 | |
IL_0033: ldloc.3 | |
IL_0034: brfalse.s IL_003f | |
IL_0036: ldloc.3 | |
IL_0037: callvirt instance void [mscorlib]System.IDisposable::Dispose() | |
IL_003c: ldnull | |
IL_003d: pop | |
IL_003e: endfinally | |
IL_003f: ldnull | |
IL_0040: pop | |
IL_0041: endfinally | |
} // end handler | |
// which ILSpy decompiled to | |
finally | |
{ | |
IDisposable disposable = r as IDisposable; | |
if (disposable != null) | |
{ | |
disposable.Dispose(); | |
} | |
} |
which is unexpected, as the compiler enforces the subject of a use
initialisation to be an IDisposable
anyway -- but, importantly, it does not test for null on the original reference.
In debug mode, all three languages differ, C# flips the test around
finally | |
{ | |
IL_001e: ldloc.1 | |
IL_001f: ldnull | |
IL_0020: ceq | |
IL_0022: stloc.2 | |
IL_0023: ldloc.2 | |
IL_0024: brtrue.s IL_002d | |
IL_0026: ldloc.1 | |
IL_0027: callvirt instance void [mscorlib]System.IDisposable::Dispose() | |
IL_002c: nop | |
IL_002d: endfinally | |
} // end handler |
VB does something a bit more like F#
finally | |
{ | |
IL_001f: ldloc.1 | |
IL_0020: ldnull | |
IL_0021: ceq | |
IL_0023: ldc.i4.0 | |
IL_0024: ceq | |
IL_0026: stloc.2 | |
IL_0027: ldloc.2 | |
IL_0028: brfalse.s IL_0031 | |
IL_002a: ldloc.1 | |
IL_002b: callvirt instance void [mscorlib]System.IDisposable::Dispose() | |
IL_0030: nop | |
IL_0031: nop | |
IL_0032: endfinally | |
} // end handler | |
// which ILSpy decompiled to (not recognising it as a Using block even when decompiling to VB!) | |
finally | |
{ | |
bool flag = r != null; | |
if (flag) | |
{ | |
((IDisposable)r).Dispose(); | |
} | |
} |
and F# just adds one of its usual bursts of gratuitous branching in the middle
finally | |
{ | |
IL_0029: ldloc.0 | |
IL_002a: isinst [mscorlib]System.IDisposable | |
IL_002f: stloc.s 4 | |
IL_0031: ldloc.s 4 | |
IL_0033: brfalse.s IL_0037 | |
IL_0035: br.s IL_0039 | |
IL_0037: br.s IL_0043 | |
IL_0039: ldloc.s 4 | |
IL_003b: callvirt instance void [mscorlib]System.IDisposable::Dispose() | |
IL_0040: ldnull | |
IL_0041: pop | |
IL_0042: endfinally | |
IL_0043: ldnull | |
IL_0044: pop | |
IL_0045: endfinally | |
} // end handler |
The C++/CLI code can rely on the stack-based object not being null, so eschews any checks
fault | |
{ | |
IL_0022: ldloc.0 | |
IL_0023: callvirt instance void [mscorlib]System.IDisposable::Dispose() | |
IL_0028: endfinally | |
} // end handler | |
IL_0029: ldloc.0 | |
IL_002a: callvirt instance void [mscorlib]System.IDisposable::Dispose() | |
// which ILSpy decompiled to | |
catch | |
{ | |
((IDisposable)r).Dispose(); | |
throw; | |
} | |
((IDisposable)r).Dispose(); |
No comments :
Post a Comment