Comportamento Garbage Collector per Destructor


9

Ho una classe semplice che è definita come di seguito.

public class Person
{
    public Person()
    {

    }

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

    ~Person()
    {
        p = this;

    }
    public static Person p;
}

Nel metodo principale

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

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

    }

Il garbage collector dovrebbe essere il riferimento principale per Person.p e quando verrà chiamato esattamente il distruttore?


Primo: un distruttore in C # è un finalizzatore . Secondo: impostare la propria istanza singleton sull'istanza che viene finalizzata sembra una pessima idea . Terzo: cos'è Person1? Vedo solo Person. Ultimo: consultare docs.microsoft.com/dotnet/csharp/programming-guide/… per come funzionano i finalizzatori.
HimBromBeere

@HimBromBeere Person1è in realtà Person, corretto l'errore di battitura.
Parimal Raj,

@HimBromBeere Questa era in realtà una domanda di intervista, ora secondo la mia comprensione, CG.Collect avrebbe dovuto invocare il distruttore, ma non è stato così.
Parimal Raj,

2
(1) Se si fa riferimento nuovamente all'oggetto finalizzato all'interno del suo finializer, NON SARÀ RACCOLTO GARBAGE fino a quando quel riferimento non sarà più raggiungibile da una radice (quindi ciò ha l'effetto di ritardare la sua garbage collection). (2) Il momento in cui viene chiamato un finalizzatore non è prevedibile.
Matthew Watson,

@HimBromBeere e quando inserisco il punto di interruzione su Console.WriteLine Person.p viene visualizzato come nullo, indipendentemente dalla GC.Collectchiamata
Parimal Raj,

Risposte:


13

La cosa che ti manca qui è che il compilatore estende la durata della tua xvariabile fino alla fine del metodo in cui è definita - è solo qualcosa che fa il compilatore - ma lo fa solo per una build DEBUG.

Se si modifica il codice in modo che la variabile sia definita in un metodo separato, funzionerà come previsto.

L'output del seguente codice è:

False
True

E il codice:

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

Quindi in sostanza la tua comprensione era corretta, ma non sapevi che il compilatore subdolo avrebbe mantenuto in vita la tua variabile fino a quando non avessi chiamato GC.Collect(), anche se l'hai impostata esplicitamente su null!

Come ho notato sopra, questo accade solo per una build DEBUG - presumibilmente in modo da poter ispezionare i valori per le variabili locali durante il debug alla fine del metodo (ma è solo una supposizione!).

Il codice originale FUNZIONA come previsto per una build di rilascio, quindi il seguente codice genera false, trueun build RELEASE e false, falseun 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.
        }
    }
}

Come addendum: si noti che se si esegue qualcosa nel finalizzatore per una classe che fa sì che un riferimento all'oggetto in fase di finalizzazione sia raggiungibile da una radice del programma, tale oggetto NON verrà raccolto in modo errato a meno che e fino a quando l'oggetto non è più riferimento.

In altre parole, è possibile assegnare a un oggetto un "arresto dell'esecuzione" tramite il finalizzatore. Questo è generalmente considerato un cattivo design, però!

Ad esempio, nel codice sopra, dove facciamo _extendMyLifetime = thisnel finalizzatore, stiamo creando un nuovo riferimento all'oggetto, quindi non sarà ora raccolto in modo _extendMyLifetimeinutile fino a quando (e qualsiasi altro riferimento) non lo farà più riferimento.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.