Perilaku Kolektor Sampah untuk Destructor

9

Saya memiliki kelas sederhana yang didefinisikan seperti di bawah ini.

public class Person
{
    public Person()
    {

    }

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

    ~Person()
    {
        p = this;

    }
    public static Person p;
}

Dalam metode Utama

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

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

    }

Apakah pengumpul sampah seharusnya menjadi referensi utama untuk Person.p dan kapan tepatnya destructor dipanggil?

Parimal Raj
sumber
Pertama: destruktor dalam C # adalah ment untuk menjadi finalizer . Kedua: menetapkan singleton-instance Anda ke instance yang difinalisasi sepertinya ide yang sangat buruk . Ketiga: apa itu Person1? Saya hanya melihat Person. Terakhir: lihat docs.microsoft.com/dotnet/csharp/programming-guide/… untuk cara kerja finalizers.
HimBromBeere
@HimBromBeere Person1sebenarnya Person, memperbaiki kesalahan ketik.
Parimal Raj
@HimBromBeere Ini sebenarnya pertanyaan wawancara, sekarang sesuai pemahaman saya, CG.Collect seharusnya memanggil destructor tetapi tidak.
Parimal Raj
2
(1) Jika Anda mereferensikan objek yang diselesaikan di dalam finializer itu, maka ITU TIDAK AKAN DIKUMPULKAN sampai referensi itu tidak lagi dapat dijangkau dari root (jadi ini memiliki efek menunda pengumpulan sampahnya). (2) Titik waktu ketika finalizer dipanggil tidak dapat diprediksi.
Matthew Watson
@HimBromBeere dan ketika saya meletakkan breakpoint di Console.WriteLine Person.p akan muncul sebagai null, terlepas dari GC.Collectpanggilan
Parimal Raj

Jawaban:

13

Hal yang Anda lewatkan di sini adalah bahwa kompiler memperpanjang masa hidup xvariabel Anda sampai akhir metode di mana ia didefinisikan - itu hanya sesuatu yang dikompilasi - tetapi hanya melakukannya untuk membangun DEBUG.

Jika Anda mengubah kode sehingga variabel didefinisikan dalam metode terpisah, itu akan berfungsi seperti yang Anda harapkan.

Output dari kode berikut adalah:

False
True

Dan kodenya:

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();
        }
    }
}

Jadi pada dasarnya pemahaman Anda benar, tetapi Anda tidak tahu bahwa kompiler licik akan membuat variabel Anda tetap hidup sampai setelah Anda menelepon GC.Collect()- bahkan jika Anda secara eksplisit mengaturnya ke nol!

Seperti yang saya sebutkan di atas, ini hanya terjadi untuk build DEBUG - mungkin sehingga Anda dapat memeriksa nilai-nilai untuk variabel lokal saat debugging ke akhir metode (tapi itu hanya dugaan!).

Kode asli TIDAK berfungsi seperti yang diharapkan untuk rilis build - jadi kode berikut ini menghasilkan false, trueuntuk build RELEASE dan false, falseuntuk build DEBUG:

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.
        }
    }
}

Sebagai tambahan: Perhatikan bahwa jika Anda melakukan sesuatu di finalizer untuk kelas yang menyebabkan referensi ke objek yang difinalisasi dapat dijangkau dari root program, maka objek itu TIDAK akan menjadi pengumpulan sampah kecuali dan sampai objek itu tidak lagi direferensikan.

Dengan kata lain, Anda dapat memberikan objek "penundaan eksekusi" melalui finalizer. Ini umumnya dianggap sebagai desain yang buruk!

Misalnya, dalam kode di atas, di mana kita melakukannya _extendMyLifetime = thisdi finalizer, kita sedang membuat referensi baru ke objek, jadi sekarang tidak akan dikumpulkan sampah sampai _extendMyLifetime(dan referensi lainnya) tidak lagi referensi itu.

Matthew Watson
sumber