Frage

Das Netz IDisposable-Muster impliziert Wenn Sie einen Finalizer schreiben und IDisposable implementieren, muss Ihr Finalizer Dispose explizit aufrufen.Das ist logisch und habe ich immer in den seltenen Situationen getan, in denen ein Finalizer erforderlich ist.

Was passiert jedoch, wenn ich einfach Folgendes mache:

class Foo : IDisposable
{
     public void Dispose(){ CloseSomeHandle(); }
}

und implementieren Sie keinen Finalizer oder ähnliches.Ruft das Framework die Dispose-Methode für mich auf?

Ja, mir ist klar, dass das dumm klingt, und die ganze Logik impliziert, dass das nicht der Fall ist, aber ich hatte immer zwei Dinge im Hinterkopf, die mich unsicher gemacht haben.

  1. Vor ein paar Jahren sagte mir einmal jemand, dass das tatsächlich der Fall sein würde, und diese Person hatte eine sehr solide Erfolgsbilanz darin, „ihre Sachen zu verstehen“.

  2. Der Compiler/Framework erledigt andere „magische“ Dinge, je nachdem, welche Schnittstellen Sie implementieren (z. B.:foreach, Erweiterungsmethoden, Serialisierung basierend auf Attributen usw.), daher macht es Sinn, dass dies auch „Magie“ sein könnte.

Obwohl ich viel darüber gelesen habe und viele Dinge angedeutet wurden, konnte ich nie etwas finden endgültig Ja- oder Nein-Antwort auf diese Frage.

War es hilfreich?

Lösung

Der .Net Garbage Collector ruft die Object.Finalize-Methode eines Objekts bei der Garbage Collection auf.Von Standard das macht Nichts und muss überschrieben werden, wenn Sie zusätzliche Ressourcen freigeben möchten.

Dispose wird NICHT automatisch aufgerufen und muss es auch sein Explizitheit Wird aufgerufen, wenn Ressourcen freigegeben werden sollen, beispielsweise innerhalb eines „using“- oder „try Finally“-Blocks

sehen http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx für mehr Informationen

Andere Tipps

Ich möchte Brians Standpunkt in seinem Kommentar hervorheben, weil er 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, kann es auch nicht sein, ob es aufgerufen wird immer heißen.

Aber das Schlimme an Finalizern ist, wie Brian sagte, dass sie dazu führen, dass Ihr Objekt eine Garbage Collection überlebt.Das kann schlimm sein.Warum?

Wie Sie vielleicht wissen, ist der GC in Generationen aufgeteilt – Gen 0, 1 und 2 sowie den Large Object Heap.Split ist ein loser Begriff – Sie erhalten einen Speicherblock, aber es gibt Hinweise darauf, wo die Objekte der Generation 0 beginnen und enden.

Der Denkprozess ist, dass Sie wahrscheinlich viele Objekte verwenden werden, die nur von kurzer Dauer sind.Daher sollte es für den GC einfach und schnell sein, auf Objekte der Generation 0 zuzugreifen.Wenn also der Speicher knapp wird, wird als Erstes eine Gen-0-Sammlung erstellt.

Wenn das nun nicht ausreichend Druck auflöst, geht es zurück und führt einen Gen-1-Sweep durch (Erneuerung von Gen 0), und wenn das immer noch nicht ausreicht, führt es einen Gen-2-Sweep durch (Erneuerung von Gen 1 und Gen 0).Daher kann das Bereinigen langlebiger Objekte eine Weile dauern und ziemlich teuer sein (da Ihre Threads während des Vorgangs möglicherweise angehalten werden).

Das heißt, wenn Sie so etwas tun:

~MyClass() { }

Ihr Objekt wird, egal was passiert, bis zur zweiten Generation überleben.Dies liegt daran, dass der GC während der Garbage Collection keine Möglichkeit hat, den Finalizer aufzurufen.Daher werden Objekte, die finalisiert werden müssen, in eine spezielle Warteschlange verschoben, um von einem anderen Thread (dem Finalizer-Thread – der, wenn Sie ihn beenden, alle möglichen schlimmen Dinge passieren) zu bereinigen.Dies bedeutet, dass Ihre Objekte länger verbleiben und möglicherweise mehr Speicherbereinigungen erzwingen.

Das alles dient also nur dazu, den Punkt zu verdeutlichen, dass Sie IDisposable verwenden möchten, um Ressourcen wann immer möglich zu bereinigen, und ernsthaft versuchen, Wege zu finden, um die Verwendung des Finalizers zu umgehen.Es liegt im besten Interesse Ihrer Bewerbung.

Hier gibt es bereits viele gute Diskussionen und ich komme etwas zu spät zur Party, aber ich wollte selbst noch ein paar Punkte hinzufügen.

  • Der Garbage Collector führt niemals direkt eine Dispose-Methode für Sie aus.
  • Der GC Wille Führen Sie Finalizer aus, wenn Ihnen danach ist.
  • Ein häufiges Muster, das für Objekte verwendet wird, die über einen Finalizer verfügen, besteht darin, eine Methode aufzurufen, die per Konvention als Dispose(bool disposing) definiert ist und „false“ übergibt, um anzugeben, dass der Aufruf aufgrund der Finalisierung und nicht aufgrund eines expliziten Dispose-Aufrufs erfolgt ist.
  • Dies liegt daran, dass es nicht sicher ist, beim Finalisieren eines Objekts Annahmen über andere verwaltete Objekte zu treffen (sie wurden möglicherweise bereits finalisiert).

class SomeObject : IDisposable {
 IntPtr _SomeNativeHandle;
 FileStream _SomeFileStream;

 // Something useful here

 ~ SomeObject() {
  Dispose(false);
 }

 public void Dispose() {
  Dispose(true);
 }

 protected virtual void Dispose(bool disposing) {
  if(disposing) {
   GC.SuppressFinalize(this);
   //Because the object was explicitly disposed, there will be no need to 
   //run the finalizer.  Suppressing it reduces pressure on the GC

   //The managed reference to an IDisposable is disposed only if the 
   _SomeFileStream.Dispose();
  }

  //Regardless, clean up the native handle ourselves.  Because it is simple a member
  // of the current instance, the GC can't have done anything to it, 
  // and this is the onlyplace to safely clean up

  if(IntPtr.Zero != _SomeNativeHandle) {
   NativeMethods.CloseHandle(_SomeNativeHandle);
   _SomeNativeHandle = IntPtr.Zero;
  }
 }
}

Das ist die einfache Version, aber es gibt viele Nuancen, die Sie bei diesem Muster zum Stolpern bringen können.

  • Der Vertrag für IDisposable.Dispose gibt an, dass es sicher sein muss, mehrmals aufzurufen (der Aufruf von Dispose für ein Objekt, das bereits entsorgt wurde, sollte nichts bewirken).
  • Es kann sehr kompliziert werden, eine Vererbungshierarchie verfügbarer Objekte ordnungsgemäß zu verwalten, insbesondere wenn verschiedene Ebenen neue verfügbare und nicht verwaltete Ressourcen einführen.Im obigen Muster ist Dispose(bool) virtuell, damit es überschrieben werden kann, damit es verwaltet werden kann, aber ich finde, dass es fehleranfällig ist.

Meiner Meinung nach ist es viel besser, alle Typen vollständig 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 sie native Ressourcen in Einwegressourcen kapseln, die intern ihre eigene Finalisierung bereitstellen (zusammen mit einer Reihe anderer Vorteile wie dem Entfernen des Fensters während P/Invoke, wo ein natives Handle aufgrund einer asynchronen Ausnahme verloren gehen könnte). .

Durch einfaches Definieren eines SafeHandle wird Folgendes trivial:


private class SomeSafeHandle
 : SafeHandleZeroOrMinusOneIsInvalid {
 public SomeSafeHandle()
  : base(true)
  { }

 protected override bool ReleaseHandle()
 { return NativeMethods.CloseHandle(handle); }
}

Ermöglicht Ihnen, den enthaltenden Typ wie folgt zu vereinfachen:


class SomeObject : IDisposable {
 SomeSafeHandle _SomeSafeHandle;
 FileStream _SomeFileStream;
 // Something useful here
 public virtual void Dispose() {
  _SomeSafeHandle.Dispose();
  _SomeFileStream.Dispose();
 }
}

Das glaube ich nicht.Sie haben die Kontrolle darüber, wann Dispose aufgerufen wird, was bedeutet, dass Sie theoretisch Entsorgungscode schreiben könnten, der Annahmen (zum Beispiel) über die Existenz anderer Objekte trifft.Sie haben keine Kontrolle darüber, wann der Finalizer aufgerufen wird. Daher wäre es fraglich, wenn der Finalizer Dispose automatisch in Ihrem Namen aufrufen würde.


BEARBEITEN:Ich bin hingegangen und habe es getestet, nur um sicherzugehen:

class Program
{
    static void Main(string[] args)
    {
        Fred f = new Fred();
        f = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Fred's gone, and he's not coming back...");
        Console.ReadLine();
    }
}

class Fred : IDisposable
{
    ~Fred()
    {
        Console.WriteLine("Being finalized");
    }

    void IDisposable.Dispose()
    {
        Console.WriteLine("Being Disposed");
    }
}

Nicht in dem von Ihnen beschriebenen Fall, aber der GC wird das anrufen Finalizer für dich, falls du einen hast.

JEDOCH.Bei der nächsten Speicherbereinigung wird das Objekt nicht gesammelt, sondern in die Finalisierungswarteschlange verschoben. Alles wird gesammelt und dann wird der Finalizer aufgerufen.Die nächste Sammlung danach wird freigegeben.

Abhängig von der Speicherauslastung Ihrer App verfügen Sie möglicherweise für eine Weile nicht über einen GC für diese Objektgenerierung.Wenn es sich beispielsweise um einen Dateistream oder eine Datenbankverbindung handelt, müssen Sie möglicherweise eine Weile warten, bis die nicht verwaltete Ressource im Finalizer-Aufruf freigegeben wird, was zu einigen Problemen führt.

Nein, es heißt nicht.

Aber das macht es einfach, die Entsorgung Ihrer Gegenstände nicht zu vergessen.Benutzen Sie einfach die using Stichwort.

Ich habe dazu folgenden Test gemacht:

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo();
        foo = null;
        Console.WriteLine("foo is null");
        GC.Collect();
        Console.WriteLine("GC Called");
        Console.ReadLine();
    }
}

class Foo : IDisposable
{
    public void Dispose()
    {

        Console.WriteLine("Disposed!");
    }

Der GC wird nicht Anruf entsorgen.Es Mai Rufen Sie Ihren Finalizer an, aber selbst dies ist nicht unter allen Umständen garantiert.

Sieh dir das an Artikel für eine Diskussion darüber, wie man damit am besten umgeht.

Die Dokumentation zu IEinwegartikel gibt eine ziemlich klare und detaillierte Erklärung des Verhaltens sowie Beispielcode.Der GC wird das NICHT aufrufen Dispose() Methode auf der Schnittstelle, aber sie ruft den Finalizer für Ihr Objekt auf.

Das IDisposable-Muster wurde in erster Linie erstellt, um vom Entwickler aufgerufen zu werden. Wenn Sie ein Objekt haben, das IDispose implementiert, sollte der Entwickler entweder das implementieren using Fügen Sie das Schlüsselwort um den Kontext des Objekts ein oder rufen Sie die Dispose-Methode direkt auf.

Die Ausfallsicherheit für das Muster besteht darin, den Finalizer zu implementieren, der die Dispose()-Methode aufruft.Wenn Sie dies nicht tun, kann es zu Speicherlecks kommen, z. B.:Wenn Sie einen COM-Wrapper erstellen und niemals das System.Runtime.Interop.Marshall.ReleaseComObject(comObject) aufrufen (das in der Dispose-Methode platziert würde).

Das automatische Aufrufen von Dispose-Methoden in der clr ist kein Zauber, außer Objekte zu verfolgen, die Finalizer enthalten, sie vom GC in der Finalizer-Tabelle zu speichern und sie aufzurufen, wenn der GC einige Bereinigungsheuristiken einsetzt.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top