Es ist wichtig, die Entsorgung von der Müllabfuhr zu trennen. Es sind völlig getrennte Dinge, mit einem gemeinsamen Punkt, auf den ich gleich noch eingehen werde.
Dispose
, Speicherbereinigung und Finalisierung
Wenn Sie eine using
Anweisung schreiben , ist dies einfach syntaktischer Zucker für einen try / finally-Block, der Dispose
auch dann aufgerufen wird, wenn der Code im Hauptteil der using
Anweisung eine Ausnahme auslöst. Dies bedeutet nicht , dass es sich bei dem Objekt um Müll handelt, der am Ende des Blocks gesammelt wird.
Bei der Entsorgung handelt es sich um nicht verwaltete Ressourcen (Nicht-Speicherressourcen). Dies können UI-Handles, Netzwerkverbindungen, Dateihandles usw. sein. Dies sind begrenzte Ressourcen. Sie möchten sie daher im Allgemeinen so bald wie möglich freigeben. Sie sollten IDisposable
immer dann implementieren, wenn Ihr Typ eine nicht verwaltete Ressource "besitzt", entweder direkt (normalerweise über a IntPtr
) oder indirekt (z. B. über a Stream
, a SqlConnection
usw.).
Bei der Speicherbereinigung selbst geht es nur um Erinnerung - mit einer kleinen Wendung. Der Garbage Collector kann Objekte finden, auf die nicht mehr verwiesen werden kann, und diese freigeben. Es wird jedoch nicht ständig nach Müll gesucht - nur dann, wenn festgestellt wird, dass dies erforderlich ist (z. B. wenn einer "Generation" des Heaps der Speicherplatz ausgeht).
Die Wendung ist die Finalisierung . Der Garbage Collector führt eine Liste von Objekten, die nicht mehr erreichbar sind, aber einen Finalizer haben (geschrieben als~Foo()
etwas verwirrend in C # - sie sind nichts anderes als C ++ - Destruktoren). Es führt die Finalizer für diese Objekte aus, nur für den Fall, dass sie eine zusätzliche Bereinigung durchführen müssen, bevor ihr Speicher freigegeben wird.
Finalizer werden fast immer verwendet, um Ressourcen zu bereinigen, wenn der Benutzer des Typs vergessen hat, sie ordnungsgemäß zu entsorgen. Wenn Sie also ein öffnen, FileStream
aber vergessen, Dispose
oder aufzurufen Close
, gibt der Finalizer schließlich das zugrunde liegende Dateihandle für Sie frei. In einem gut geschriebenen Programm sollten Finalizer meiner Meinung nach fast nie feuern.
Setzen einer Variablen auf null
Ein kleiner Punkt beim Setzen einer Variablen auf null
- dies ist für die Speicherbereinigung fast nie erforderlich. Vielleicht möchten Sie es manchmal tun, wenn es sich um eine Mitgliedsvariable handelt, obwohl es meiner Erfahrung nach selten vorkommt, dass ein "Teil" eines Objekts nicht mehr benötigt wird. Wenn es sich um eine lokale Variable handelt, ist die JIT normalerweise intelligent genug (im Release-Modus), um zu wissen, wann Sie keine Referenz mehr verwenden werden. Beispielsweise:
StringBuilder sb = new StringBuilder();
sb.Append("Foo");
string x = sb.ToString();
// The string and StringBuilder are already eligible
// for garbage collection here!
int y = 10;
DoSomething(y);
// These aren't helping at all!
x = null;
sb = null;
// Assume that x and sb aren't used here
Es kann sich lohnen, eine lokale Variable festzulegen, null
wenn Sie sich in einer Schleife befinden und einige Zweige der Schleife die Variable verwenden müssen, aber Sie wissen, dass Sie einen Punkt erreicht haben, an dem Sie dies nicht tun. Beispielsweise:
SomeObject foo = new SomeObject();
for (int i=0; i < 100000; i++)
{
if (i == 5)
{
foo.DoSomething();
// We're not going to need it again, but the JIT
// wouldn't spot that
foo = null;
}
else
{
// Some other code
}
}
IDisposable / Finalizer implementieren
Sollten Ihre eigenen Typen Finalizer implementieren? Mit ziemlicher Sicherheit nicht. Wenn Sie nur indirekt über nicht verwaltete Ressourcen verfügen (z. FileStream
B. über eine Mitgliedsvariable), hilft das Hinzufügen eines eigenen Finalizers nicht weiter: Der Stream ist mit ziemlicher Sicherheit für die Speicherbereinigung geeignet, wenn sich Ihr Objekt befindet, sodass Sie sich einfach darauf verlassen können FileStream
einen Finalizer haben (falls erforderlich - er kann sich auf etwas anderes beziehen usw.). Wenn Sie eine nicht verwaltete Ressource "fast" direkt halten möchten, SafeHandle
ist Ihr Freund - es dauert ein bisschen, bis Sie damit beginnen, aber es bedeutet, dass Sie fast nie wieder einen Finalizer schreiben müssen . Normalerweise sollten Sie einen Finalizer nur benötigen, wenn Sie eine Ressource (an IntPtr
) wirklich direkt im Griff haben und sich umsehen möchtenSafeHandle
sobald Sie können. (Dort gibt es zwei Links - lesen Sie im Idealfall beide.)
Joe Duffy hat eine sehr lange Reihe von Richtlinien zu Finalisierern und IDisposable (zusammen mit vielen intelligenten Leuten geschrieben), die es wert sind, gelesen zu werden. Es ist zu beachten, dass das Versiegeln Ihrer Klassen das Leben erheblich erleichtert: Das Muster des Überschreibens Dispose
zum Aufrufen einer neuen virtuellen Dispose(bool)
Methode usw. ist nur relevant, wenn Ihre Klasse für die Vererbung ausgelegt ist.
Dies war ein kleiner Streifzug, aber bitte fragen Sie nach, wo Sie welche haben möchten :)
Wenn Sie ein Objekt entsorgen, werden die Ressourcen freigegeben. Wenn Sie einer Variablen null zuweisen, ändern Sie nur eine Referenz.
Nachdem Sie dies ausgeführt haben, ist das Objekt, auf das sich myclass bezog, noch vorhanden und wird fortgesetzt, bis der GC damit fertig ist, es zu bereinigen. Wenn Dispose explizit aufgerufen wird oder sich in einem using-Block befindet, werden alle Ressourcen so schnell wie möglich freigegeben.
quelle
Die beiden Operationen haben nicht viel miteinander zu tun. Wenn Sie einen Verweis auf null setzen, geschieht dies einfach. Es hat an sich keinen Einfluss auf die Klasse, auf die überhaupt verwiesen wurde. Ihre Variable zeigt einfach nicht mehr auf das Objekt, das sie verwendet hat, sondern das Objekt selbst bleibt unverändert.
Wenn Sie Dispose () aufrufen, handelt es sich um einen Methodenaufruf für das Objekt selbst. Was auch immer die Dispose-Methode tut, wird jetzt für das Objekt ausgeführt. Dies hat jedoch keinen Einfluss auf Ihre Referenz auf das Objekt.
Der einzige Überlappungsbereich besteht darin, dass, wenn keine Verweise mehr auf ein Objekt vorhanden sind, möglicherweise Müll gesammelt wird. Und wenn die Klasse die IDisposable-Schnittstelle implementiert, wird Dispose () für das Objekt aufgerufen, bevor es mit Müll gesammelt wird.
Dies geschieht jedoch aus zwei Gründen nicht sofort, nachdem Sie Ihre Referenz auf null gesetzt haben. Erstens können andere Referenzen vorhanden sein, sodass noch kein Müll gesammelt wird, und zweitens passiert nichts, selbst wenn dies die letzte Referenz war, sodass es jetzt bereit ist, Müll zu sammeln, bis der Garbage Collector sich zum Löschen entscheidet das Objekt.
Das Aufrufen von Dispose () für ein Objekt "tötet" das Objekt in keiner Weise. Es wird allgemein aufzuräumen , so dass das Objekt verwendet kann sicher wieder gelöscht werden, aber schließlich gibt es nichts Magisches an entsorgen, es ist nur eine Klassenmethode.
quelle