デストラクタのガベージコレクタの動作


9

以下のように定義された単純なクラスがあります。

public class Person
{
    public Person()
    {

    }

    public override string ToString()
    {
        return "I Still Exist!";
    }

    ~Person()
    {
        p = this;

    }
    public static Person p;
}

メインメソッドで

    public static void Main(string[] args)
    {
        var x = new Person();
        x = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(Person.p == null);

    }

ガベージコレクターはPerson.pの主な参照を想定していますか?また、いつデストラクタが呼び出されるのですか?


まず、C#のデストラクタはファイナライザになることです。次に、ファイナライズされるインスタンスにシングルトンインスタンスを設定することは、非常に悪い考えのようです。第三:何Person1ですか?しか見えないPerson。最後:ファイナライザの動作については、docs.microsoft.com / dotnet / csharp / programming-guide /…を参照してください。
HimBromBeere

@HimBromBeere Person1は実際Personには、タイプミスを修正しました。
Parimal Raj

@HimBromBeereこれは実際にはインタビューの質問でしたが、私の理解によると、CG.Collectはデストラクタを呼び出したはずですが、呼び出しませんでした。
Parimal Raj

2
(1)ファイナライザ内でファイナライズされているオブジェクトを再参照すると、その参照がルートから到達できなくなるまでガベージコレクションされません(そのため、ガベージコレクションの遅延の影響があります)。(2)ファイナライザが呼び出される時点は予測できません。
マシューワトソン

@HimBromBeereとブレークポイントをConsole.WriteLineに配置すると、GC.Collect呼び出しに関係なくPerson.pがnullとして表示される
Parimal Raj

回答:


13

ここで欠けているのは、コンパイラーがx変数のライフタイムを、それが定義されているメソッドの最後まで延長していることです。これは、コンパイラーが行うことの1つですが、DEBUGビルドの場合にのみ行われます。

変数が別のメソッドで定義されるようにコードを変更すると、期待どおりに機能します。

次のコードの出力は次のとおりです。

False
True

そしてコード:

using System;

namespace ConsoleApp1
{
    class Finalizable
    {
        ~Finalizable()
        {
            _extendMyLifetime = this;
        }

        public static bool LifetimeExtended => _extendMyLifetime != null;

        static Finalizable _extendMyLifetime;
    }

    class Program
    {
        public static void Main()
        {
            test();

            Console.WriteLine(Finalizable.LifetimeExtended); // False.

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

            Console.WriteLine(Finalizable.LifetimeExtended); // True.
        }

        static void test()
        {
            new Finalizable();
        }
    }
}

だから、基本的にはあなたの理解は正しかったが、あなたは卑劣なコンパイラがするまで、生きているあなたの変数を維持するつもりだったことを知らなかった、あなたが呼ばれるGC.Collect()-あなたは明示的にヌルに設定した場合でも!

上で述べたように、これはDEBUGビルドでのみ発生します。おそらく、メソッドの最後までデバッグしながらローカル変数の値を調べることができます(ただし、これは単なる推測です)。

元のコードはリリースビルドで期待どおりに機能するため、次のコードfalse, trueはRELEASEビルドとfalse, falseDEBUGビルドで出力されます。

using System;

namespace ConsoleApp1
{
    class Finalizable
    {
        ~Finalizable()
        {
            _extendMyLifetime = this;
        }

        public static bool LifetimeExtended => _extendMyLifetime != null;

        static Finalizable _extendMyLifetime;
    }

    class Program
    {
        public static void Main()
        {
            new Finalizable();

            Console.WriteLine(Finalizable.LifetimeExtended); // False.

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

            Console.WriteLine(Finalizable.LifetimeExtended); // True iff RELEASE build.
        }
    }
}

補遺として:ファイナライザで、ファイナライズされているオブジェクトへの参照がプログラムルートから到達可能になるような処理を行うと、そのオブジェクトがなくなるまで、そのオブジェクトはガベージコレクションされないことに注意してください。参照。

つまり、ファイナライザを介してオブジェクトに「実行の継続」を与えることができます。しかし、これは一般的に悪いデザインだと考えられています!

たとえば_extendMyLifetime = this、ファイナライザで行う上記のコードでは、オブジェクトへの新しい参照を作成しているため、オブジェクト_extendMyLifetime(および他の参照)がオブジェクトを参照しなくなるまで、ガベージコレクションされません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.