Поведение сборщика мусора для деструктора

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 Person.p появляется как ноль, независимо от GC.Collectвызова
Parimal Raj

Ответы:

13

Единственное, чего вам здесь не хватает, так это того, что компилятор продлевает время жизни вашей xпеременной до конца метода, в котором она определена - это то, что компилятор делает - но он делает это только для сборки 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(и любая другая ссылка) больше не ссылается на него.

Мэтью Уотсон
источник