0

Object.Finalize() をオーバーライドするクラスが存在する場合の GC.Collect() の動作を理解できないようです。これは私の基本コードです:

namespace test
{
 class Foo
 {
  ~Foo() { Console.WriteLine("Inside Foo.Finalize()"); }
 }

 static class Program
 {

  static void Main()
  {
   {
    Foo bar = new Foo();
   }

   GC.Collect();
   GC.WaitForPendingFinalizers();

   Console.ReadLine();
  }
 }

}

予想に反して、プログラムの終了時にのみコンソール出力を取得し、終了後は取得しませんGC.WaitForPendingFinalizers()

4

5 に答える 5

12

コンパイラもランタイムも、スコープ外のローカルが実際にその内容の存続期間を切り捨てることを保証する必要はありません。ライフタイムを計算する目的で、コンパイラまたはランタイムが中括弧が存在しないかのようにこれを処理することは完全に合法です。ブレース ベースのクリーンアップが必要な場合は、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 は自由に参照を再利用できます。

于 2009-11-11T16:34:41.863 に答える
3

ガベージ コレクターは、データがいつ収集されるかについてまったく保証しません。usingこれが、破棄可能なオブジェクトを破棄するためにステートメントを使用する必要がある理由の 1 つです。

GC.WaitForPendingFinalizers() は、収集されたファイナライザーのみを待機します。オブジェクトがまだ収集されていない場合、何もしません。

ほとんどの場合、名前にアクセスできなくても、コンパイラはバーへのポインターを保持しています。

new Foo() への呼び出しを別の関数に入れてみます-これも役立つかもしれませんが、保証はありません.

于 2009-11-11T16:33:54.897 に答える
2

bar は、呼び出し時にスコープ内にGC.Collect()あり、GC.WaitForPendingFinalizers()

Foo も実装していませんIDisposable()

私の推測では、GC はまだ Foo オブジェクトで使用されているメモリを解放する準備ができておらず、明示的に呼び出すことはできませんDispose()。したがって、アプリケーションの実行が完了すると破棄されます。

于 2009-11-11T16:34:58.233 に答える
0

これは、コード実行の予期しないポイントで発生する可能性のあるGCに関するもう1つの優れた記事です。

ライフタイム、GC.KeepAlive、リサイクルの処理-cbrumme http://blogs.msdn.com/b/cbrumme/archive/2003/04/19/51365.aspx?wa=wsignin1.0

私の質問は、記事に記載されている時点で強制GCをどのように再現できるかということです。OperateOnHandle()の先頭にGC.Collect()を配置しようとしましたが、クラスCのデストラクタを定義しましたが、機能していないようです。デストラクタは、プログラムの最後に常に呼び出されます。

于 2010-09-19T11:22:19.343 に答える
0

スコープは C++ と同じようには機能しないと思います。関数が終了するまで、変数は実際には有効だと思います。たとえば、次のようになります。

class Program
{
    class Foo
    {
        ~Foo() { Console.WriteLine("Test"); }
    }


    static void Test()
    {
        Foo foo = new Foo();
    }

    static void Main()
    {
        Test();

        GC.Collect();
        GC.WaitForPendingFinalizers();

        Console.ReadLine();
    }
}

IL について考えると、IL にはブレースのようなものはなく、ローカル変数には常に少なくとも関数スコープがあります。

于 2009-11-11T16:35:31.413 に答える