Das .NET IDisposable-Muster impliziert , dass Ihr Finalizer Dispose explizit aufrufen muss, wenn Sie einen Finalizer schreiben und IDisposable implementieren. Dies ist logisch und das habe ich immer in den seltenen Situationen getan, in denen ein Finalizer erforderlich ist.
Was passiert jedoch, wenn ich dies einfach mache:
class Foo : IDisposable
{
public void Dispose(){ CloseSomeHandle(); }
}
und implementieren Sie keinen Finalizer oder so. Wird das Framework die Dispose-Methode für mich aufrufen?
Ja, mir ist klar, dass dies dumm klingt, und jede Logik impliziert, dass dies nicht der Fall ist, aber ich hatte immer zwei Dinge im Hinterkopf, die mich unsicher gemacht haben.
Jemand hat mir vor ein paar Jahren einmal gesagt, dass dies tatsächlich der Fall sein würde, und diese Person hatte eine sehr solide Erfolgsbilanz darin, "ihre Sachen zu kennen".
Der Compiler / das Framework führt andere "magische" Dinge aus, je nachdem, welche Schnittstellen Sie implementieren (z. B. foreach, Erweiterungsmethoden, Serialisierung basierend auf Attributen usw.). Daher ist es sinnvoll, dass dies auch "magisch" ist.
Obwohl ich viel darüber gelesen habe und viele Dinge impliziert wurden, konnte ich auf diese Frage nie eine endgültige Ja- oder Nein-Antwort finden.
quelle
Ich möchte Brians Punkt in seinem Kommentar hervorheben, weil es wichtig ist.
Finalizer sind keine deterministischen Destruktoren wie in C ++. Wie andere bereits betont haben, gibt es keine Garantie dafür, wann es aufgerufen wird, und wenn Sie über genügend Speicher verfügen, wird es jemals aufgerufen.
Aber das Schlechte an Finalisierern ist, dass Ihr Objekt, wie Brian sagte, eine Speicherbereinigung überlebt. Das kann schlecht sein. Warum?
Wie Sie vielleicht wissen oder nicht wissen, ist der GC in Generationen unterteilt - Gen 0, 1 und 2 sowie den Heap für große Objekte. Split ist ein loser Begriff - Sie erhalten einen Speicherblock, aber es gibt Hinweise darauf, wo die Gen 0-Objekte beginnen und enden.
Der Denkprozess ist, dass Sie wahrscheinlich viele Objekte verwenden werden, die nur von kurzer Dauer sind. Diese sollten also für den GC einfach und schnell sein, um zu - Gen 0-Objekten zu gelangen. Wenn also Speicherdruck herrscht, ist das erste, was es tut, eine Gen 0-Sammlung.
Wenn das nicht genug Druck auflöst, geht es zurück und führt einen Gen 1-Sweep durch (Wiederherstellen von Gen 0). Wenn dies immer noch nicht ausreicht, führt es einen Gen 2-Sweep durch (Wiederherstellen von Gen 1 und Gen 0). Das Reinigen langlebiger Objekte kann daher eine Weile dauern und ziemlich teuer sein (da Ihre Fäden während des Vorgangs möglicherweise hängen bleiben).
Dies bedeutet, wenn Sie so etwas tun:
Ihr Objekt wird, egal was passiert, der 2. Generation entsprechen. Dies liegt daran, dass der GC den Finalizer während der Speicherbereinigung nicht aufrufen kann. Objekte, die finalisiert werden müssen, werden in eine spezielle Warteschlange verschoben, um von einem anderen Thread (dem Finalizer-Thread - der beim Töten alle möglichen schlechten Dinge verursacht) gelöscht zu werden. Dies bedeutet, dass Ihre Objekte länger herumhängen und möglicherweise mehr Speicherbereinigungen erzwingen.
All dies dient nur dazu, den Punkt nach Hause zu bringen, an dem Sie IDisposable verwenden möchten, um Ressourcen zu bereinigen, wann immer dies möglich ist, und ernsthaft zu versuchen, mit dem Finalizer Wege zu finden. Es ist im besten Interesse Ihrer Anwendung.
quelle
Hier gibt es bereits viele gute Diskussionen, und ich bin etwas spät zur Party, aber ich wollte selbst ein paar Punkte hinzufügen.
Das ist die einfache Version, aber es gibt viele Nuancen, die Sie über dieses Muster stolpern lassen können.
Meiner Meinung nach ist es viel besser, Typen zu vermeiden, die direkt sowohl verfügbare Referenzen als auch native Ressourcen enthalten, die möglicherweise finalisiert werden müssen. SafeHandles bieten eine sehr saubere Möglichkeit, dies zu tun, indem native Ressourcen in Einwegressourcen gekapselt werden, die intern ihre eigene Finalisierung bereitstellen (zusammen mit einer Reihe anderer Vorteile wie dem Entfernen des Fensters während P / Invoke, bei dem ein natives Handle aufgrund einer asynchronen Ausnahme verloren gehen könnte). .
Das einfache Definieren eines SafeHandle macht diese Trivialität:
Ermöglicht die Vereinfachung des enthaltenen Typs auf:
quelle
GC.SuppressFinalize
in diesem Beispiel. In diesem Zusammenhang sollte SuppressFinalize nur aufgerufen werden, wenn esDispose(true)
erfolgreich ausgeführt wird. WennDispose(true)
irgendwann nach der Unterdrückung der Finalisierung ein Fehler auftritt, aber bevor alle Ressourcen (insbesondere nicht verwaltete) bereinigt werden, möchten Sie dennoch die Finalisierung durchführen, um so viele Bereinigungen wie möglich durchzuführen. Es ist besser, denGC.SuppressFinalize
Aufruf nach dem Aufruf von in dieDispose()
Methode zu verschiebenDispose(true)
. Siehe Framework Design Guidelines und diesen Beitrag .Das glaube ich nicht. Sie haben die Kontrolle darüber, wann Dispose aufgerufen wird. Dies bedeutet, dass Sie theoretisch Entsorgungscode schreiben können, der Annahmen über (zum Beispiel) die Existenz anderer Objekte macht. Sie haben keine Kontrolle darüber, wann der Finalizer aufgerufen wird. Daher ist es zweifelhaft, wenn der Finalizer Dispose automatisch in Ihrem Namen aufruft.
EDIT: Ich ging weg und testete, nur um sicherzugehen:
quelle
Nicht in dem von Ihnen beschriebenen Fall, aber der GC ruft den Finalizer für Sie an, falls Sie einen haben.
JEDOCH. Bei der nächsten Garbage Collection wird das Objekt nicht gesammelt, sondern in die Finalisierungswarteschlange gestellt, alles wird gesammelt und der Finalizer aufgerufen. Die nächste Sammlung danach wird freigegeben.
Abhängig vom Speicherdruck Ihrer App haben Sie möglicherweise für eine Weile keinen GC für diese Objekterzeugung. Im Fall eines Dateistreams oder einer Datenbankverbindung müssen Sie möglicherweise eine Weile warten, bis die nicht verwaltete Ressource im Finalizer-Aufruf für eine Weile freigegeben wird, was zu Problemen führt.
quelle
Nein, es heißt nicht.
Dies macht es jedoch leicht, nicht zu vergessen, Ihre Gegenstände zu entsorgen. Verwenden Sie einfach die
using
Schlüsselwort.Ich habe dazu folgenden Test durchgeführt:
quelle
Der GC ruft nicht dispose auf. Es kann Ihre Finalizerthread nennen, aber auch dies ist nicht unter allen Umständen garantiert.
In diesem Artikel erfahren Sie, wie Sie am besten damit umgehen können.
quelle
Die Dokumentation zu IDisposable enthält eine ziemlich klare und detaillierte Erläuterung des Verhaltens sowie Beispielcode. Der GC ruft die
Dispose()
Methode NICHT auf der Schnittstelle auf, sondern den Finalizer für Ihr Objekt.quelle
Das IDisposable-Muster wurde hauptsächlich erstellt, um vom Entwickler aufgerufen zu werden. Wenn Sie ein Objekt haben, das IDispose implementiert, sollte der Entwickler entweder das implementieren
using
Schlüsselwort um den Kontext des Objekts oder die Dispose-Methode direkt aufrufen.Die Ausfallsicherheit für das Muster besteht darin, den Finalizer zu implementieren, der die Dispose () -Methode aufruft. Wenn Sie dies nicht tun, können Sie einige Speicherlecks verursachen, z. B.: Wenn Sie einen COM-Wrapper erstellen und niemals System.Runtime.Interop.Marshall.ReleaseComObject (comObject) aufrufen (das in der Dispose-Methode platziert wird).
Es gibt keine Magie in der CLR, Dispose-Methoden automatisch aufzurufen, außer Objekte zu verfolgen, die Finalizer enthalten, und diese in der Finalizer-Tabelle vom GC zu speichern und sie aufzurufen, wenn einige Bereinigungsheuristiken vom GC aktiviert werden.
quelle