Поведінка сміттєзбірника для руйнівника


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, falseдля збірки 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.
        }
    }
}

Як додаток: Зауважте, що якщо ви робите щось у фіналізаторі для класу, що спричиняє посилання на об'єкт, який завершується, бути доступним з кореня програми, то цей об’єкт НЕ буде збирати сміття, якщо тільки до цього об'єкта більше не буде посилання.

Іншими словами, ви можете дати об'єкту "зупинити виконання" через фіналізатор. Це, як правило, вважається поганим дизайном!

Наприклад, у наведеному вище коді, де ми робимо _extendMyLifetime = thisу фіналізаторі, ми створюємо нове посилання на об’єкт, тому він тепер не буде збирати сміття, поки _extendMyLifetime(і будь-яка інша посилання) більше не посилається на нього.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.