コンパイラもランタイムも、スコープ外のローカルが実際にその内容の存続期間を切り捨てることを保証する必要はありません。ライフタイムを計算する目的で、コンパイラまたはランタイムが中括弧が存在しないかのようにこれを処理することは完全に合法です。ブレース ベースのクリーンアップが必要な場合は、IDisposable を実装し、"using" ブロックを使用します。
アップデート:
「最適化されたビルドと最適化されていないビルドでこれが異なるのはなぜですか」という質問に関しては、codegen の違いを見てください。
最適化されていない:
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 28 (0x1c)
.maxstack 1
.locals init (class test.Foo V_0)
IL_0000: nop
IL_0001: nop
IL_0002: newobj instance void test.Foo::.ctor()
IL_0007: stloc.0
IL_0008: nop
IL_0009: call void [mscorlib]System.GC::Collect()
IL_000e: nop
IL_000f: call void [mscorlib]System.GC::WaitForPendingFinalizers()
IL_0014: nop
IL_0015: call string [mscorlib]System.Console::ReadLine()
IL_001a: pop
IL_001b: ret
} // end of method Program::Main
最適化:
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 23 (0x17)
.maxstack 8
IL_0000: newobj instance void test.Foo::.ctor()
IL_0005: pop
IL_0006: call void [mscorlib]System.GC::Collect()
IL_000b: call void [mscorlib]System.GC::WaitForPendingFinalizers()
IL_0010: call string [mscorlib]System.Console::ReadLine()
IL_0015: pop
IL_0016: ret
} // end of method Program::Main
明らかに大きな違いです。明らかに、最適化されていないビルドでは、参照はローカル スロット 0 に格納され、メソッドが終了するまで削除されません。したがって、GC はメソッドが終了するまでメモリを再利用できません。最適化されたビルドでは、参照はスタックに格納され、すぐにスタックからポップされます。スタックに有効な参照が残っていないため、GC は自由に参照を再利用できます。