Garbage Collector-Verhalten für Destructor


9

Ich habe eine einfache Klasse, die wie folgt definiert ist.

public class Person
{
    public Person()
    {

    }

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

    ~Person()
    {
        p = this;

    }
    public static Person p;
}

In der Hauptmethode

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

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

    }

Soll der Garbage Collector die Hauptreferenz für Person.p sein und wann genau wird der Destruktor aufgerufen?


Erstens: Ein Destruktor in C # soll ein Finalizer sein . Zweitens: Das Setzen Ihrer Singleton-Instanz auf die zu finalisierende Instanz scheint eine sehr, sehr schlechte Idee zu sein . Drittens: Was ist Person1? Ich sehe nur Person. Zuletzt: Informationen zur Funktionsweise von Finalisierern finden Sie unter docs.microsoft.com/dotnet/csharp/programming-guide/… .
HimBromBeere

@HimBromBeere Person1ist eigentlich Personder Tippfehler behoben.
Parimal Raj

@HimBromBeere Dies war eigentlich eine Interviewfrage, nach meinem Verständnis hätte CG.Collect den Destruktor aufrufen sollen, tat es aber nicht.
Parimal Raj

2
(1) Wenn Sie das Objekt, das in seinem Finializer finalisiert wird, erneut referenzieren, wird es erst dann gesammelt, wenn diese Referenz von einem Stamm aus nicht mehr erreichbar ist (dies hat also den Effekt, dass die Speicherbereinigung verzögert wird). (2) Der Zeitpunkt, zu dem ein Finalizer aufgerufen wird, ist nicht vorhersehbar.
Matthew Watson

@HimBromBeere und wenn ich Haltepunkt bei Console.WriteLine Person.p setze, wird als null GC.Collect
angezeigt

Antworten:


13

Was Ihnen hier fehlt, ist, dass der Compiler die Lebensdauer Ihrer xVariablen bis zum Ende der Methode verlängert, in der sie definiert ist - das ist nur etwas, was der Compiler tut -, aber nur für einen DEBUG-Build.

Wenn Sie den Code so ändern, dass die Variable in einer separaten Methode definiert wird, funktioniert sie wie erwartet.

Die Ausgabe des folgenden Codes lautet:

False
True

Und der Code:

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

Im Grunde war Ihr Verständnis also richtig, aber Sie wussten nicht, dass der hinterhältige Compiler Ihre Variable bis nach Ihrem Aufruf am Leben erhalten würde GC.Collect()- selbst wenn Sie sie explizit auf null setzen!

Wie oben erwähnt, geschieht dies nur für einen DEBUG-Build - vermutlich, damit Sie die Werte beim Debuggen bis zum Ende der Methode auf lokale Variablen überprüfen können (aber das ist nur eine Vermutung!).

Der ursprüngliche Code funktioniert wie erwartet für einen Release-Build. Daher wird der folgende Code false, truefür einen RELEASE-Build und false, falseeinen DEBUG-Build ausgegeben:

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.
        }
    }
}

Als Ergänzung: Beachten Sie, dass, wenn Sie im Finalizer etwas für eine Klasse tun, das bewirkt, dass ein Verweis auf das zu finalisierende Objekt von einem Programmstamm aus erreichbar ist, dieses Objekt NICHT durch Müll gesammelt wird, es sei denn und bis dieses Objekt nicht mehr vorhanden ist referenziert.

Mit anderen Worten, Sie können einem Objekt über den Finalizer einen "Ausführungsaufenthalt" geben. Dies wird jedoch allgemein als schlechtes Design angesehen!

Zum Beispiel erstellen wir _extendMyLifetime = thisim obigen Code, wie wir es im Finalizer tun, einen neuen Verweis auf das Objekt, sodass es jetzt erst dann mit Müll gesammelt wird, wenn _extendMyLifetime(und jeder andere Verweis) nicht mehr darauf verweist.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.