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 :)