Der Punkt der Entsorgung besteht darin , nicht verwaltete Ressourcen freizugeben. Es muss irgendwann gemacht werden, sonst werden sie nie aufgeräumt. Der Garbage Collector weiß nicht, wie er DeleteHandle()
eine Variable vom Typ aufruft IntPtr
, er weiß nicht, ob er aufrufen muss oder nicht DeleteHandle()
.
Hinweis : Was ist eine nicht verwaltete Ressource ? Wenn Sie es in Microsoft .NET Framework gefunden haben: Es wird verwaltet. Wenn Sie sich selbst in MSDN umgesehen haben, ist es nicht verwaltet. Alles, was Sie mit P / Invoke-Aufrufen verwendet haben, um aus der komfortablen Welt von allem, was Ihnen in .NET Framework zur Verfügung steht, herauszukommen, wird nicht verwaltet - und Sie sind jetzt für die Bereinigung verantwortlich.
Das von Ihnen erstellte Objekt muss eine Methode verfügbar machen , die die Außenwelt aufrufen kann, um nicht verwaltete Ressourcen zu bereinigen. Die Methode kann beliebig benannt werden:
public void Cleanup()
oder
public void Shutdown()
Stattdessen gibt es einen standardisierten Namen für diese Methode:
public void Dispose()
Es wurde sogar eine Schnittstelle erstellt IDisposable
, die nur diese eine Methode hat:
public interface IDisposable
{
void Dispose()
}
So machen Sie Ihr Objekt die IDisposable
Schnittstelle und versprechen auf diese Weise, dass Sie diese einzige Methode geschrieben haben, um Ihre nicht verwalteten Ressourcen zu bereinigen:
public void Dispose()
{
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
Und du bist fertig. Außer du kannst es besser machen.
Was ist, wenn Ihr Objekt eine 250 MB System.Drawing.Bitmap (dh die von .NET verwaltete Bitmap-Klasse) als eine Art Frame-Puffer zugewiesen hat ? Sicher, dies ist ein verwaltetes .NET-Objekt, und der Garbage Collector gibt es frei. Aber möchten Sie wirklich 250 MB Speicher nur dort sitzen lassen und darauf warten, dass der Garbage Collector es tut? irgendwann kommt und ihn ? Was ist, wenn eine offene Datenbankverbindung besteht ? Sicherlich wollen wir nicht, dass diese Verbindung offen bleibt und darauf wartet, dass der GC das Objekt fertigstellt.
Wenn der Benutzer angerufen hat Dispose()
(was bedeutet, dass er das Objekt nicht mehr verwenden möchte), warum nicht diese verschwenderischen Bitmaps und Datenbankverbindungen loswerden?
Also jetzt werden wir:
- nicht verwaltete Ressourcen loswerden (weil wir müssen), und
- Verwaltete Ressourcen loswerden (weil wir hilfreich sein wollen)
Aktualisieren wir also unsere Dispose()
Methode, um diese verwalteten Objekte zu entfernen:
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
Und alles ist gut, außer du kannst es besser machen !
Was ist, wenn die Person vergessen hat , Dispose()
Ihr Objekt anzurufen ? Dann würden sie einige nicht verwaltete Ressourcen verlieren !
Hinweis: Sie verlieren keine verwalteten Ressourcen, da der Garbage Collector möglicherweise in einem Hintergrundthread ausgeführt wird und den mit nicht verwendeten Objekten verknüpften Speicher freigibt. Dies umfasst Ihr Objekt und alle von Ihnen verwendeten verwalteten Objekte (z. B. das Bitmap
und das DbConnection
).
Wenn die Person vergessen hat anzurufen Dispose()
, können wir trotzdem ihren Speck retten! Wir haben immer noch eine Möglichkeit, es für sie zu nennen : Wenn der Garbage Collector endlich dazu kommt, unser Objekt freizugeben (dh abzuschließen).
Hinweis: Der Garbage Collector gibt schließlich alle verwalteten Objekte frei. Wenn dies der Fall ist, wird die Finalize
Methode für das Objekt aufgerufen. Der GC kennt Ihre Entsorgungsmethode nicht oder kümmert sich nicht darum . Das war nur ein Name, den wir für eine Methode gewählt haben, die wir aufrufen, wenn wir nicht verwaltete Dinge loswerden wollen.
Die Zerstörung unseres Objekts durch den Garbage Collector ist der perfekte Zeitpunkt, um diese lästigen, nicht verwalteten Ressourcen freizugeben. Wir tun dies, indem wir die Finalize()
Methode überschreiben .
Hinweis: In C # überschreiben Sie die Finalize()
Methode nicht explizit . Sie schreiben eine Methode, die wie ein C ++ - Destruktor aussieht , und der Compiler nimmt dies als Ihre Implementierung der Finalize()
Methode an:
~MyObject()
{
//we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
Dispose(); //<--Warning: subtle bug! Keep reading!
}
Aber dieser Code enthält einen Fehler. Sie sehen, der Garbage Collector läuft auf einem Hintergrund-Thread . Sie kennen nicht die Reihenfolge, in der zwei Objekte zerstört werden. Es ist durchaus möglich, dass in Ihrem Dispose()
Code das verwaltete Objekt, das Sie entfernen möchten (weil Sie hilfreich sein wollten), nicht mehr vorhanden ist:
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
this.frameBufferImage = null;
}
}
Sie müssen also Finalize()
feststellen, Dispose()
dass keine verwalteten Ressourcen berührt werden dürfen (da diese möglicherweise nicht vorhanden sind) sind), während freigegeben werden.
Das Standardmuster , dies zu tun zu haben ist Finalize()
und Dispose()
beide Anruf eine dritte Methode (!); Wenn Sie ein boolesches Sprichwort übergeben, von dem aus Sie es aufrufen Dispose()
(im Gegensatz zu Finalize()
), bedeutet dies, dass verwaltete Ressourcen sicher freigegeben werden können.
Diese interne Methode könnte einen beliebigen Namen wie "CoreDispose" oder "MyInternalDispose" erhalten, wird aber traditionell so genannt Dispose(Boolean)
:
protected void Dispose(Boolean disposing)
Ein hilfreicherer Parametername könnte jedoch sein:
protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too, but only if I'm being called from Dispose
//(If I'm being called from Finalize then the objects might not exist
//anymore
if (itIsSafeToAlsoFreeManagedObjects)
{
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
}
Und Sie ändern Ihre Implementierung der IDisposable.Dispose()
Methode in:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
}
und Ihr Finalizer für:
~MyObject()
{
Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}
Hinweis : Wenn Ihr Objekt von einem Objekt abstammt, das implementiert wird Dispose
, vergessen Sie nicht, die Basis- Dispose-Methode aufzurufen , wenn Sie Dispose überschreiben:
public override void Dispose()
{
try
{
Dispose(true); //true: safe to free managed resources
}
finally
{
base.Dispose();
}
}
Und alles ist gut, außer du kannst es besser machen !
Wenn der Benutzer Dispose()
Ihr Objekt aufruft , wurde alles bereinigt. Später, wenn der Garbage Collector vorbeikommt und Finalize aufruft, wird er Dispose
erneut aufgerufen .
Dies ist nicht nur verschwenderisch, sondern wenn Ihr Objekt Junk-Verweise auf Objekte enthält, die Sie bereits beim letzten Aufruf entsorgt haben Dispose()
, werden Sie versuchen, diese erneut zu entsorgen!
Sie werden feststellen, dass ich in meinem Code darauf geachtet habe, Verweise auf Objekte zu entfernen, die ich entsorgt habe, damit ich nicht versuche, sie aufzurufen Dispose
eine Junk-Objektreferenz . Aber das hinderte einen subtilen Käfer nicht daran, sich einzuschleichen.
Wenn der Benutzer Folgendes aufruft Dispose()
: Das Handle CursorFileBitmapIconServiceHandle wird zerstört. Später, wenn der Garbage Collector ausgeführt wird, versucht er, denselben Handle erneut zu zerstören.
protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy
...
}
Wenn Sie dies beheben, teilen Sie dem Garbage Collector mit, dass er sich nicht um die Fertigstellung des Objekts kümmern muss. Die Ressourcen wurden bereits bereinigt, und es ist keine weitere Arbeit erforderlich. Sie tun dies, indem Sie GC.SuppressFinalize()
die Dispose()
Methode aufrufen :
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}
Nachdem der Benutzer angerufen hat Dispose()
, haben wir:
- nicht verwaltete Ressourcen freigegeben
- freigegebene verwaltete Ressourcen
Es macht keinen Sinn, dass der GC den Finalizer ausführt - alles ist erledigt.
Könnte ich Finalize nicht verwenden, um nicht verwaltete Ressourcen zu bereinigen?
Die Dokumentation für Object.Finalize
sagt:
Die Finalize-Methode wird verwendet, um Bereinigungsvorgänge für nicht verwaltete Ressourcen durchzuführen, die vom aktuellen Objekt gehalten werden, bevor das Objekt zerstört wird.
In der MSDN-Dokumentation heißt es jedoch auch IDisposable.Dispose
:
Führt anwendungsdefinierte Aufgaben aus, die mit dem Freigeben, Freigeben oder Zurücksetzen nicht verwalteter Ressourcen verbunden sind.
Also was ist es? An welchem Ort kann ich nicht verwaltete Ressourcen bereinigen? Die Antwort ist:
Es ist deine Wahl! Aber wähle Dispose
.
Sie könnten Ihre nicht verwaltete Bereinigung sicherlich in den Finalizer stellen:
~MyObject()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//A C# destructor automatically calls the destructor of its base class.
}
Das Problem dabei ist, dass Sie keine Ahnung haben, wann der Garbage Collector Ihr Objekt fertigstellen wird. Ihre nicht verwalteten, nicht benötigten und nicht verwendeten nativen Ressourcen bleiben erhalten, bis der Garbage Collector schließlich ausgeführt wird . Dann wird Ihre Finalizer-Methode aufgerufen. Bereinigen nicht verwalteter Ressourcen. Die Dokumentation von Object.Finalize weist darauf hin:
Der genaue Zeitpunkt, zu dem der Finalizer ausgeführt wird, ist nicht definiert. Implementieren Sie eine Close- Methode oder stellen Sie eine IDisposable.Dispose
Implementierung bereit , um eine deterministische Freigabe von Ressourcen für Instanzen Ihrer Klasse sicherzustellen .
Dies ist die Tugend der Dispose
Bereinigung nicht verwalteter Ressourcen. Sie erfahren und steuern, wann nicht verwaltete Ressourcen bereinigt werden. Ihre Zerstörung ist "deterministisch" .
Um Ihre ursprüngliche Frage zu beantworten: Warum nicht jetzt Speicher freigeben, anstatt wenn der GC sich dazu entscheidet? Ich habe eine Gesichtserkennungs - Software , dass Bedürfnisse von 530 MB interner Bilder loszuwerden jetzt , da sie nicht mehr benötigt werden . Wenn wir es nicht tun: Die Maschine kommt zum Stillstand.
Bonuslesung
Für alle, die den Stil dieser Antwort mögen (das Warum erklären , damit das Wie offensichtlich wird), empfehle ich Ihnen, Kapitel 1 von Don Box's Essential COM zu lesen:
Auf 35 Seiten erklärt er die Probleme bei der Verwendung von Binärobjekten und erfindet COM vor Ihren Augen. Sobald Sie das Warum von COM erkannt haben, sind die verbleibenden 300 Seiten offensichtlich und beschreiben lediglich die Implementierung von Microsoft.
Ich denke, jeder Programmierer, der sich jemals mit Objekten oder COM befasst hat, sollte zumindest das erste Kapitel lesen. Es ist die beste Erklärung für alles, was es je gab.
Extra Bonus Lesung
Wenn alles, was Sie wissen, von Eric Lippert falsch ist
Es ist daher in der Tat sehr schwierig, einen korrekten Finalizer zu schreiben, und der beste Rat, den ich Ihnen geben kann, ist, es nicht zu versuchen .