11

私が持っているとしましょう

void foo () {
    Bar bar = new Bar(); // bar is never referred to after this line
    // (1)
    doSomethingWithoutBar();
}

(1)でbar指しているオブジェクトはガベージコレクション対象か? それともbar、範囲外にする必要がありますか? GC.Collectによって呼び出された場合、違いはありdoSomethingWithoutBarますか?

これは、Bar に (C#) デストラクタまたはそのようなファンキーなものがあるかどうかを知ることに関連しています。

4

5 に答える 5

8

オブジェクトは、使用されなくなることが確実になるとすぐに、ガベージ コレクションの対象になる可能性があります。bar変数がスコープ外になる前にガベージ コレクションされる可能性は十分にあります。

証拠:

using System;

class Bar
{
    ~Bar() { Console.WriteLine("Finalized!"); }
}

class Program
{
    static void Main(string[] args)
    {
        Bar bar = new Bar();
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Press any key to exit...");
        Console.ReadLine();
    }
}

リリース モードで実行します(デバッグ モードでは収集されないため)。

出力:

確定!
終了するには、任意のキーを押してください...

Mono を使用するideoneでも動作します。出力は同じです。

于 2012-07-13T21:57:35.840 に答える
3

仕様をざっと読むと、実装固有のようです。ガベージ コレクションは許可されていますが、必須ではありません。

これは、 ECMA Specのセクション 10.9「自動メモリ管理」のメモから取得します。

[注: 実装は、オブジェクトへのどの参照を将来使用できるかを判断するためにコードを分析することを選択する場合があります。たとえば、スコープ内にあるローカル変数がオブジェクトへの唯一の既存の参照であるが、そのローカル変数が、プロシージャ内の現在の実行ポイントからの実行の可能な継続で参照されることがない場合、実装は可能性があります (ただし、そうではありません) 。オブジェクトをもはや使用されていないものとして扱う必要があります。エンドノート】

鉱山を強調します。

于 2012-07-13T22:01:54.563 に答える
1

参照している CLR のバージョンを定義しないと、ここで表示される動作について明確にすることは不可能です。

この例では、次のことが当てはまると仮定して、仮想CLR を作成できます。

  1. のコンストラクタは何もしBarません
  2. 初期化されるフィールドはありません (つまり、オブジェクトの構築に潜在的な副作用はありません)。

Bar bar = new Bar();「何もしない」ため、行を完全に無視して最適化します。

私の記憶の限りでは、CLR の現在のバージョンでは、bar構築した直後にガベージ コレクションの対象となります。

于 2012-07-13T22:01:20.550 に答える
1

これは間違いなく発生する可能性があります。たとえば、コンストラクターの実行中にインスタンスをファイナライズできることを示すデモを次に示します。

class Program
{
    private static int _lifeState;
    private static bool _end;

    private sealed class Schrodinger
    {
        private int _x;

        public Schrodinger()
        {
            //Here I'm using 'this'
            _x = 1;

            //But now I no longer reference 'this'
            _lifeState = 1;

            //Keep busy to provide an opportunity for GC to collect me
            for (int i=0;i<10000; i++)
            {
                var garbage = new char[20000];
            }

            //Did I die before I finished being constructed?
            if (Interlocked.CompareExchange(ref _lifeState, 0, 1) == 2)
            {
                Console.WriteLine("Am I dead or alive?");
                _end = true;
            }
        }

        ~Schrodinger()
        {
            _lifeState = 2;
        }
    }

    static void Main(string[] args)
    {
        //Keep the GC churning away at finalization to demonstrate the case
        Task.Factory.StartNew(() =>
            {
                while (!_end)
                {
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                }
            });

        //Keep constructing cats until we find the desired case
        int catCount = 0;
        while (!_end)
        {
            catCount++;
            var cat = new Schrodinger();
            while (_lifeState != 2)
            {
                Thread.Yield();
            }
        }
        Console.WriteLine("{0} cats died in the making of this boundary case", catCount);
        Console.ReadKey();
    }
}

これを機能させるには、リリース ビルドを生成して Visual Studio の外部で実行する必要があります (そうしないと、デバッガーが効果を妨げるコードを挿入します)。.NET 4.0 x64 をターゲットとする VS 2010 でこれをテストしました。

'keep busy' ループの繰り返しを微調整して、Cat が完成する前に終了する確率に影響を与えることができます。

于 2012-07-13T22:55:45.457 に答える
1

マークは質問に答えましたが、ここに解決策があります:

void foo () {
    Bar bar = new Bar(); // bar is never referred to after this line
    // (1)
    doSomethingWithoutBar();

    GC.KeepAlive(bar); // At the point where you no longer need it
}
于 2012-07-13T22:36:55.773 に答える