Zachowanie śmieciarza dla niszczyciela

9

Mam prostą klasę, która jest zdefiniowana jak poniżej.

public class Person
{
    public Person()
    {

    }

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

    ~Person()
    {
        p = this;

    }
    public static Person p;
}

W metodzie głównej

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

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

    }

Czy śmieciarz powinien być głównym źródłem Person.p i kiedy dokładnie zostanie wywołany destruktor?

Parimal Raj
źródło
Po pierwsze: destruktor w C # ma być finalizatorem . Po drugie: ustawienie instancji singleton na finalizowaną instancję wydaje się bardzo złym pomysłem . Po trzecie: co to jest Person1? Widzę tylko Person. Na koniec : zobacz docs.microsoft.com/dotnet/csharp/programming-guide/…, aby dowiedzieć się, jak działają finalizatory.
HimBromBeere
@HimBromBeere Person1jest Personnaprawione literówka.
Parimal Raj
@HimBromBeere To było właściwie pytanie do wywiadu, teraz według mojego zrozumienia CG.Collect powinien był wywołać destruktor, ale tak nie było.
Parimal Raj,
2
(1) Jeśli ponownie odniesiesz się do finalizowanego obiektu w jego finializatorze, NIE ZBĘDZIE SIĘ ZBIORNIKA, dopóki referencja nie będzie już osiągalna z katalogu głównego (skutkuje to opóźnieniem zbierania śmieci). (2) Nie można przewidzieć momentu, w którym wywoływany jest finalizator.
Matthew Watson
@HimBromBeere i kiedy ustawię punkt przerwania w Console.WriteLine Person.p zbliża się do zera, niezależnie od GC.Collectpołączenia
Parimal Raj,

Odpowiedzi:

13

Brakuje tutaj tego, że kompilator wydłuża czas życia xzmiennej aż do końca metody, w której jest zdefiniowany - to jest po prostu kompilator - ale robi to tylko dla kompilacji DEBUG.

Jeśli zmienisz kod, tak aby zmienna została zdefiniowana w oddzielnej metodzie, będzie działać zgodnie z oczekiwaniami.

Dane wyjściowe następującego kodu to:

False
True

I kod:

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

Więc w zasadzie zrozumienie było poprawne, ale nie wiedziałem, że podstępne kompilator zamiar utrzymać przy życiu dopóki zmienna po zadzwoniłeś GC.Collect()- nawet jeśli jawnie ustawić go na null!

Jak zauważyłem powyżej, dzieje się tak tylko w przypadku kompilacji DEBUG - przypuszczalnie po to, aby sprawdzić wartości zmiennych lokalnych podczas debugowania do końca metody (ale to tylko przypuszczenie!).

Oryginalny kod NIE działa zgodnie z oczekiwaniami w przypadku kompilacji wydania - więc następujące dane wyjściowe false, truedla kompilacji RELEASE i false, falsekompilacji 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.
        }
    }
}

Jako uzupełnienie: Zauważ, że jeśli zrobisz coś w finalizatorze dla klasy, która powoduje, że odwołanie do finalizowanego obiektu jest osiągalne z katalogu głównego programu, wówczas ten obiekt NIE zostanie wyrzucony, dopóki i ten obiekt już nie będzie o którym mowa.

Innymi słowy, można nadać obiektowi „wstrzymanie wykonania” za pośrednictwem finalizatora. Jest to jednak ogólnie uważane za zły projekt!

Na przykład w powyższym kodzie, w którym robimy to _extendMyLifetime = thisw finalizatorze, tworzymy nowe odwołanie do obiektu, więc nie będzie ono teraz zbierane w pamięci, dopóki _extendMyLifetime(i wszelkie inne odniesienia) nie będą już do niego odwoływać.

Matthew Watson
źródło