Поведение сборщика мусора для деструктора - PullRequest
9 голосов
/ 14 октября 2019

У меня есть простой класс, который определен как показано ниже.

public class Person
{
    public Person()
    {

    }

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

    ~Person()
    {
        p = this;

    }
    public static Person p;
}

В методе Main

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

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

    }

Должен ли сборщик мусора быть главной ссылкой на Person.p и когда именнодеструктор называться?

1 Ответ

13 голосов
/ 14 октября 2019

Здесь вам не хватает того, что компилятор продлевает время жизни вашей переменной 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() - даже если вы явно установили его в null!

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

...