it-swarm.com.de

Ordnungsgemäße Verwendung der IDisposable-Schnittstelle

Ich weiß aus dem Lesen von der Microsoft-Dokumentation , dass die "primäre" Verwendung der IDisposable -Schnittstelle darin besteht, nicht verwaltete Ressourcen zu bereinigen.

Für mich bedeutet "nicht verwaltet" Dinge wie Datenbankverbindungen, Sockets, Fenstergriffe usw. Aber ich habe Code gesehen, in dem die Methode Dispose() implementiert ist, um verwaltete zu befreien Ressourcen, die mir überflüssig erscheinen, da der Müllmann das für Sie erledigen sollte.

Zum Beispiel:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

Meine Frage ist, macht dies den von MyCollection verwendeten freien Speicher des Garbage Collectors schneller als normalerweise?

edit : Bisher haben die Leute einige gute Beispiele für die Verwendung von IDisposable veröffentlicht, um nicht verwaltete Ressourcen wie Datenbankverbindungen und Bitmaps zu bereinigen. Angenommen, _theList im obigen Code enthält eine Million Zeichenfolgen, und Sie möchten diesen Speicher jetzt freigeben , anstatt auf den Garbage Collector zu warten. Würde der obige Code das erreichen?

1551
cwick

Der Entsorgungspunkt ist, um nicht verwaltete Ressourcen freizugeben. Es muss irgendwann gemacht werden, sonst werden sie nie aufgeräumt. Der Garbage Collector weiß nicht wie, um DeleteHandle() für eine Variable vom Typ IntPtr aufzurufen. Er weiß nicht ob oder nicht, um DeleteHandle() aufzurufen.

Anmerkung: 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 außerhalb der komfortablen Welt von allem, was Ihnen in .NET Framework zur Verfügung steht, verwendet haben, ist nicht verwaltet - und Sie sind jetzt für die Bereinigung verantwortlich.

Das von Ihnen erstellte Objekt muss eine Methode verfügbar machen, die von der Außenwelt aufgerufen werden 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 namens IDisposable erstellt, die nur diese eine Methode enthält:

_public interface IDisposable
{
   void Dispose()
}
_

Auf diese Weise legen Sie fest, dass Ihr Objekt die Schnittstelle IDisposable verfügbar macht, und versprechen, dass Sie diese einzige Methode zur Bereinigung Ihrer nicht verwalteten Ressourcen geschrieben haben:

_public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
_

Und du bist fertig. Außer du kannst es besser machen.


Was ist, wenn Ihr Objekt 250 MB System.Drawing.Bitmap (d. H. Die in .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 wollen Sie wirklich 250MB Speicher einfach stehen lassen und darauf warten, dass der Garbage Collector irgendwann mitkommt und ihn freigibt? Was ist, wenn es eine offene Datenbankverbindung gibt? Sicherlich möchten wir nicht, dass diese Verbindung offen steht und darauf wartet, dass der GC das Objekt finalisiert.

Wenn der Benutzer Dispose() aufgerufen hat (was bedeutet, dass er das Objekt nicht mehr verwenden möchte), warum nicht diese verschwenderischen Bitmaps und Datenbankverbindungen entfernen?

Also werden wir jetzt:

  • nicht verwaltete Ressourcen loswerden (weil wir müssen) und
  • verwaltete Ressourcen loswerden (weil wir hilfreich sein wollen)

Aktualisieren wir also unsere Methode Dispose(), 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 vergessenDispose() für Ihr Objekt aufruft? Dann würden sie einige nicht verwaltete Ressourcen auslaufen lassen!

Hinweis: Sie verlieren keine verwalteten Ressourcen, da der Garbage Collector schließlich in einem Hintergrundthread ausgeführt wird und den mit nicht verwendeten Objekten verknüpften Speicher freigibt. Dies schließt Ihr Objekt und alle verwalteten Objekte ein, die Sie verwenden (z. B. Bitmap und DbConnection).

Wenn die Person vergessen hat, Dispose() anzurufen, können wir noch ihren Speck retten! Wir haben immer noch eine Möglichkeit, sie für zu nennen: Wenn der Garbage Collector endlich dazu kommt, unser Objekt freizugeben (d. H. Abzuschließen).

Hinweis: Der Garbage Collector gibt schließlich alle verwalteten Objekte frei. In diesem Fall wird die Methode Finalize für das Objekt aufgerufen. Der GC kennt oder kümmert sich nicht um Ihre Dispose -Methode. Das war nur ein Name, den wir für eine Methode gewählt haben, die wir aufrufen, um nicht verwaltete Dinge loszuwerden.

Die Zerstörung unseres Objekts durch den Garbage Collector ist die perfekte Zeit, um diese lästigen, nicht verwalteten Ressourcen freizugeben. Wir tun dies, indem wir die Methode Finalize() überschreiben.

Anmerkung: In C # überschreiben Sie die Methode Finalize() nicht explizit. Sie schreiben eine Methode, die wie ein C++ - Destruktor aussieht, und der Compiler betrachtet dies als Ihre Implementierung der Methode Finalize():

_~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 die Reihenfolge, in der zwei Objekte zerstört werden, nicht. Es ist durchaus möglich, dass in Ihrem Dispose()-Code das managed -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;
   }
}
_

Was Sie also brauchen, ist eine Möglichkeit für Finalize(), Dispose() mitzuteilen, dass es keine verwalteten Ressourcen berühren soll (weil sie möglicherweise nicht mehr vorhanden sind ), während immer noch nicht verwaltete Ressourcen frei.

Das Standardmuster dafür ist, dass sowohl Finalize() als auch Dispose() eine third (!) -Methode aufrufen. Wenn Sie ein boolesches Sprichwort übergeben, wenn Sie es von Dispose() aufrufen (im Gegensatz zu Finalize()), bedeutet dies, dass es sicher ist, verwaltete Ressourcen freizugeben.

Diese interne Methode könnte einen willkürlichen Namen wie "CoreDispose" oder "MyInternalDispose" erhalten, wird aber traditionell als Dispose(Boolean) bezeichnet. :

_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 Methode IDisposable.Dispose() in:

_public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}
_

und dein Finalizer an:

_~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}
_

Note: Wenn Ihr Objekt von einem Objekt abstammt, das Dispose implementiert, vergessen Sie nicht, die base 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() für Ihr Objekt aufruft, wurde alles bereinigt. Wenn der Garbage Collector später vorbeikommt und Finalize aufruft, ruft er erneut Dispose auf.

Dies ist nicht nur verschwenderisch, sondern wenn Ihr Objekt Junk-Verweise auf Objekte enthält, die Sie bereits aus dem last ​​Aufruf von Dispose() entsorgt haben, werden Sie versuchen, sie erneut zu entsorgen!

Sie werden feststellen, dass ich in meinem Code sorgfältig darauf geachtet habe, Verweise auf Objekte zu entfernen, die ich entsorgt habe, sodass ich nicht versuche, Dispose für einen Junk-Objektverweis aufzurufen. Aber das hat nicht verhindert, dass sich ein subtiler Käfer eingeschlichen hat.

Wenn der Benutzer Dispose() aufruft, wird das Handle CursorFileBitmapIconServiceHandle zerstört. Wenn der Garbage Collector später ausgeführt wird, versucht er, denselben Punkt erneut zu zerstören.

_protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}
_

Die Art und Weise, wie Sie dies beheben, besteht darin, dem Garbage Collector mitzuteilen, dass er das Objekt nicht finalisieren muss - seine Ressourcen wurden bereits bereinigt, und es ist keine weitere Arbeit erforderlich. Dazu rufen Sie GC.SuppressFinalize() in der Methode Dispose() auf:

_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 Dispose() aufgerufen hat, haben wir:

  • freigegebene nicht verwaltete Ressourcen
  • freigegebene verwaltete Ressourcen

Es hat keinen Sinn, den Finalizer im GC auszuführen - alles ist erledigt.

Könnte ich Finalize nicht verwenden, um nicht verwaltete Ressourcen zu bereinigen?

In der Dokumentation zu Object.Finalize heißt es:

Mit der Finalize-Methode werden Bereinigungsvorgänge für nicht verwaltete Ressourcen ausgeführt, die sich im aktuellen Objekt befinden, bevor das Objekt zerstört wird.

In der MSDN-Dokumentation heißt es jedoch auch für 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 welcher Stelle kann ich nicht verwaltete Ressourcen bereinigen? Die Antwort ist:

Es ist deine Wahl! Aber wählen Sie Dispose.

Sie könnten Ihre nicht verwaltete Bereinigung auf jeden Fall 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 finalisieren wird. Ihre nicht verwalteten, nicht benötigten, 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:

Die genaue Uhrzeit, zu der der Finalizer ausgeführt wird, ist nicht definiert. Implementieren Sie eine Close -Methode oder eine IDisposable.Dispose -Implementierung, um eine deterministische Freigabe von Ressourcen für Instanzen Ihrer Klasse sicherzustellen.

Dies ist der Vorteil von Dispose, um nicht verwaltete Ressourcen zu bereinigen. Sie lernen und steuern, wann nicht verwaltete Ressourcen bereinigt werden. Ihre Zerstörung ist "deterministisch" .


Zur Beantwortung Ihrer ursprünglichen Frage: Warum nicht jetzt Speicher freigeben und nicht erst, wenn der GC dies beschließt? Ich habe eine Gesichtserkennungssoftware, die benötigt , um 530 MB an internen Bildern zu entfernen jetzt, da sie nicht mehr benötigt werden. Wenn wir das nicht tun: Die Maschine kommt zum Stillstand.

Bonus Lesung

Für alle, die den Stil dieser Antwort mögen (Erklären des Warum , damit das Wie offensichtlich wird), empfehle ich, Kapitel zu lesen Eines der wichtigsten COMs von Don Box:

Auf 35 Seiten erklärt er die Probleme bei der Verwendung von binären Objekten 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 jemals gab.

Extra Bonus Lesung

Wenn alles, was Sie wissen, falsch ist von Eric Lippert

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.

2472
Ian Boyd

IDisposable wird häufig verwendet, um die Anweisung using auszunutzen und eine einfache Möglichkeit zur deterministischen Bereinigung verwalteter Objekte zu nutzen.

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}
58
yfeldblum

Der Zweck des Dispose-Musters besteht darin, einen Mechanismus zum Bereinigen sowohl verwalteter als auch nicht verwalteter Ressourcen bereitzustellen. Der Zeitpunkt hängt davon ab, wie die Dispose-Methode aufgerufen wird. In Ihrem Beispiel hat die Verwendung von Dispose keine Auswirkungen auf die Entsorgung, da das Löschen einer Liste keine Auswirkungen auf die zu entsorgende Sammlung hat. Ebenso haben die Aufrufe zum Setzen der Variablen auf Null keine Auswirkungen auf den GC.

In diesem Artikel finden Sie weitere Informationen zum Implementieren des Dispose-Musters. Im Grunde sieht es jedoch so aus:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Die hier wichtigste Methode ist die Dispose (bool), die tatsächlich unter zwei verschiedenen Umständen ausgeführt wird:

  • disposing == true: Die Methode wurde direkt oder indirekt vom Code eines Benutzers aufgerufen. Verwaltete und nicht verwaltete Ressourcen können entsorgt werden.
  • disposing == false: Die Methode wurde von der Laufzeit im Finalizer aufgerufen, und Sie sollten nicht auf andere Objekte verweisen. Es können nur nicht verwaltete Ressourcen entsorgt werden.

Das Problem beim einfachen Ausführen der Bereinigung durch den GC besteht darin, dass Sie nicht wirklich steuern können, wann der GC einen Erfassungszyklus ausführt (Sie können GC.Collect () aufrufen, sollten dies jedoch nicht tun), sodass möglicherweise Ressourcen verbleiben herum länger als nötig. Denken Sie daran, dass der Aufruf von Dispose () keinen Sammlungszyklus auslöst oder den GC in irgendeiner Weise veranlasst, das Objekt zu sammeln/freizugeben. Es bietet lediglich die Möglichkeit, die verwendeten Ressourcen deterministischer zu bereinigen und dem GC mitzuteilen, dass diese Bereinigung bereits durchgeführt wurde.

Bei IDisposable und dem Entsorgungsmuster geht es nicht darum, sofort Speicher freizugeben. Das einzige Mal, dass ein Aufruf von Dispose tatsächlich sogar die Möglichkeit hat, sofort Speicher freizugeben, ist das Behandeln des Szenarios disposing == false und das Manipulieren nicht verwalteter Ressourcen. Bei verwaltetem Code wird der Speicher erst dann zurückgefordert, wenn der GC einen Erfassungszyklus ausführt, über den Sie wirklich keine Kontrolle haben (abgesehen vom Aufrufen von GC.Collect (), das ich bereits erwähnt habe, ist keine gute Idee).

Ihr Szenario ist nicht wirklich gültig, da Zeichenfolgen in .NET keine unveränderten Ressourcen verwenden und IDisposable nicht implementieren. Es gibt keine Möglichkeit, sie zur Bereinigung zu zwingen.

39
Scott Dorman

Nach dem Aufrufen von Dispose sollten die Methoden eines Objekts nicht mehr aufgerufen werden (obwohl ein Objekt weitere Aufrufe von Dispose tolerieren sollte). Daher ist das Beispiel in der Frage albern. Wenn Dispose aufgerufen wird, kann das Objekt selbst verworfen werden. Der Benutzer sollte also nur alle Verweise auf das gesamte Objekt verwerfen (auf null setzen), und alle zugehörigen internen Objekte werden automatisch bereinigt.

Was die allgemeine Frage zu verwaltet/nicht verwaltet und die Diskussion in anderen Antworten anbelangt, muss jede Antwort auf diese Frage mit einer Definition einer nicht verwalteten Ressource beginnen.

Es läuft darauf hinaus, dass es eine Funktion gibt, die Sie aufrufen können, um das System in einen Zustand zu versetzen, und es gibt eine weitere Funktion, die Sie aufrufen können, um es aus diesem Zustand zurückzusetzen. Im typischen Beispiel könnte die erste eine Funktion sein, die ein Datei-Handle zurückgibt, und die zweite könnte ein Aufruf von CloseHandle sein.

Aber - und das ist der Schlüssel - es könnte sich um jedes passende Funktionspaar handeln. Der eine baut einen Staat auf, der andere reißt ihn nieder. Wenn der Status erstellt, aber noch nicht abgerissen wurde, ist eine Instanz der Ressource vorhanden. Sie müssen dafür sorgen, dass der Abbau zum richtigen Zeitpunkt erfolgt - die Ressource wird nicht von der CLR verwaltet. Der einzige automatisch verwaltete Ressourcentyp ist Speicher. Es gibt zwei Arten: den GC und den Stack. Werttypen werden vom Stapel verwaltet (oder durch Anhängen an Referenztypen), und Referenztypen werden vom GC verwaltet.

Diese Funktionen können Statusänderungen verursachen, die frei verschachtelt werden können oder perfekt verschachtelt sein müssen. Die Statusänderungen können threadsicher sein oder auch nicht.

Schauen Sie sich das Beispiel in der Frage von Justice an. Änderungen am Einzug der Protokolldatei müssen perfekt verschachtelt sein, da sonst alles schief geht. Es ist auch unwahrscheinlich, dass sie threadsicher sind.

Es ist möglich, eine Fahrt mit dem Müllsammler zu unternehmen, um Ihre nicht verwalteten Ressourcen zu bereinigen. Aber nur wenn die Zustandsänderungsfunktionen threadsicher sind und zwei Zustände eine Lebensdauer haben, die sich in irgendeiner Weise überlappt. Das Beispiel von Justice für eine Ressource darf also KEINEN Finalizer haben! Es würde einfach niemandem helfen.

Für diese Arten von Ressourcen können Sie IDisposable ohne Finalizer implementieren. Der Finalizer ist absolut optional - das muss sein. Dies wird in vielen Büchern beschönigt oder gar nicht erwähnt.

Sie müssen dann die Anweisung using verwenden, um sicherzustellen, dass Dispose aufgerufen wird. Dies ist im Wesentlichen so, als würde man einen Ride mit dem Stack verbinden (so wie Finalizer zum GC gehört, ist using zum Stack).

Der fehlende Teil ist, dass Sie Dispose manuell schreiben und es auf Ihre Felder und Ihre Basisklasse aufrufen müssen. C++/CLI-Programmierer müssen das nicht tun. Der Compiler schreibt es in den meisten Fällen für sie.

Es gibt eine Alternative, die ich für Staaten bevorzuge, die perfekt verschachtelt und nicht threadsicher sind (abgesehen von allem anderen erspart Ihnen die Vermeidung von IDisposable das Problem, mit jemandem zu streiten, der nicht widerstehen kann, jeder Klasse, die IDisposable implementiert, einen Finalizer hinzuzufügen). .

Anstatt eine Klasse zu schreiben, schreiben Sie eine Funktion. Die Funktion akzeptiert einen Delegaten zum Rückruf an:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

Und dann wäre ein einfaches Beispiel:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

Das Lambda, das übergeben wird, dient als Codeblock. Sie erstellen also eine eigene Kontrollstruktur, die dem gleichen Zweck wie using dient, mit der Ausnahme, dass Sie keine Gefahr mehr haben, dass der Anrufer sie missbraucht. Es gibt keine Möglichkeit, die Ressource zu bereinigen.

Diese Technik ist weniger nützlich, wenn es sich um eine Ressource handelt, die möglicherweise überlappende Lebensdauern hat, da Sie in der Lage sein möchten, Ressource A, dann Ressource B, dann Ressource A und später Ressource B zu beenden. Dies ist nicht möglich wenn Sie den Benutzer gezwungen haben, so perfekt zu nisten. Aber dann müssen Sie IDisposable verwenden (aber immer noch ohne Finalizer, es sei denn, Sie haben Threadsafety implementiert, was nicht kostenlos ist).

17

Szenarien, in denen ich IDisposable verwende: Bereinigen von nicht verwalteten Ressourcen, Abbestellen von Ereignissen, Schließen von Verbindungen

Das Idiom, mit dem ich IDisposable implementiere (nicht threadsicher):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}
14
olli-MSFT

Ja, dieser Code ist vollständig redundant und unnötig und lässt den Garbage Collector nichts tun, was er sonst nicht tun würde (wenn eine Instanz von MyCollection den Gültigkeitsbereich verlässt, d. H.). Insbesondere die .Clear() -Aufrufe.

Antwort auf deine Änderung: Art von. Wenn ich das mache:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Für die Speicherverwaltung ist dies funktional identisch:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Wenn Sie den Speicher wirklich wirklich wirklich sofort freigeben müssen, rufen Sie GC.Collect() auf. Es gibt jedoch keinen Grund, dies hier zu tun. Der Speicher wird freigegeben, wenn er benötigt wird.

11
mqp

Wenn MyCollection ohnehin mit Müll entsorgt wird, müssen Sie ihn nicht entsorgen. Andernfalls wird die CPU mehr als erforderlich ausgelastet, und möglicherweise werden einige vorberechnete Analysen, die der Garbage Collector bereits durchgeführt hat, ungültig.

Ich verwende IDisposable, um sicherzustellen, dass Threads korrekt entsorgt werden, zusammen mit nicht verwalteten Ressourcen.

EDIT Als Antwort auf Scotts Kommentar:

Das einzige Mal, wenn die GC-Leistungsmetriken betroffen sind, erfolgt ein Aufruf von GC.Collect (). "

Konzeptionell behält der GC eine Ansicht des Objektreferenzgraphen und aller Verweise auf ihn aus den Stapelrahmen von Threads bei. Dieser Heap kann sehr groß sein und viele Seiten des Speichers umfassen. Als Optimierung speichert der GC die Analyse von Seiten im Cache, von denen unwahrscheinlich ist, dass sie sich sehr oft ändern, um ein unnötiges erneutes Scannen der Seite zu vermeiden. Der GC erhält eine Benachrichtigung vom Kernel, wenn sich Daten auf einer Seite ändern, sodass er weiß, dass die Seite verschmutzt ist und einen erneuten Scan erfordert. Wenn sich die Sammlung in Gen0 befindet, ändern sich wahrscheinlich auch andere Elemente auf der Seite, dies ist jedoch in Gen1 und Gen2 weniger wahrscheinlich. Ausnahmsweise waren diese Hooks in Mac OS X für das Team, das den GC auf Mac portiert hat, nicht verfügbar, damit das Silverlight-Plug-in auf dieser Plattform funktioniert.

Ein weiterer Punkt gegen die unnötige Entsorgung von Ressourcen: Stellen Sie sich eine Situation vor, in der ein Prozess entladen wird. Stellen Sie sich auch vor, dass der Prozess schon einige Zeit läuft. Möglicherweise wurden viele der Speicherseiten dieses Prozesses auf die Festplatte ausgelagert. Zumindest befinden sie sich nicht mehr im L1- oder L2-Cache. In einer solchen Situation macht es keinen Sinn, dass eine entladende Anwendung all diese Daten und Codepages zurück in den Speicher verschiebt, um Ressourcen freizugeben, die ohnehin vom Betriebssystem freigegeben werden, wenn der Prozess beendet wird. Dies gilt für verwaltete und sogar bestimmte nicht verwaltete Ressourcen. Es müssen nur Ressourcen entsorgt werden, die keine Hintergrund-Threads am Leben erhalten. Andernfalls bleibt der Prozess am Leben.

Jetzt gibt es während der normalen Ausführung kurzlebige Ressourcen, die ordnungsgemäß bereinigt werden müssen (wie @fezmonkey auf Datenbankverbindungen, Sockets, Fenster-Handles hinweist), um nicht verwaltet zu werden Speicherlecks. Dies sind die Dinge, die entsorgt werden müssen. Wenn Sie eine Klasse erstellen, die einen Thread besitzt (und damit meine ich, dass sie ihn erstellt hat und daher dafür verantwortlich ist, dass er zumindest aufgrund meines Codierungsstils stoppt), muss diese Klasse höchstwahrscheinlich IDisposable implementieren und abreißen Faden runter während Dispose.

Das .NET Framework verwendet die Schnittstelle IDisposable als Signal, auch als Warnung, an Entwickler, dass diese Klasse entsorgt werden muss . Ich kann mir keine Typen im Framework vorstellen, die IDisposable implementieren (ausgenommen explizite Schnittstellenimplementierungen), bei denen die Entsorgung optional ist.

11
Drew Noakes

Wenn Sie jetzt löschen möchten , verwenden Sie nicht verwalteten Speicher .

Sehen:

7
franckspike

In dem Beispiel, das Sie gepostet haben, wird "der Speicher jetzt nicht freigegeben". Der gesamte Speicher ist müllsammelbar, aber es kann möglich sein, dass der Speicher früher gesammelt wird Generation . Sie müssten einige Tests durchführen, um sicherzugehen.


Die Framework Design Guidelines sind Richtlinien und keine Regeln. Hier erfahren Sie, wozu die Benutzeroberfläche in erster Linie dient, wann sie verwendet wird, wie sie verwendet wird und wann sie nicht verwendet wird.

Ich habe einmal Code gelesen, der ein einfaches RollBack () bei einem Fehler mit IDisposable war. Die unten stehende MiniTx-Klasse würde ein Flag bei Dispose () prüfen, und wenn der Commit -Aufruf niemals stattgefunden hätte, würde sie Rollback für sich selbst aufrufen. Es wurde eine Indirektionsebene hinzugefügt, durch die der aufrufende Code viel einfacher zu verstehen und zu warten ist. Das Ergebnis sah ungefähr so ​​aus:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

Ich habe auch gesehen, dass Timing-/Logging-Code dasselbe macht. In diesem Fall hat die Dispose () -Methode den Timer gestoppt und protokolliert, dass der Block beendet wurde.

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

Im Folgenden finden Sie einige konkrete Beispiele, die keine Bereinigung nicht verwalteter Ressourcen durchführen, sondern IDisposable erfolgreich zum Erstellen von saubererem Code verwenden.

6
Robert Paulson

Ich werde nicht die üblichen Dinge über das Verwenden oder Freigeben nicht verwalteter Ressourcen wiederholen, die alle behandelt wurden. Aber ich möchte darauf hinweisen, was ein weit verbreitetes Missverständnis zu sein scheint.
Mit folgendem Code

 Public Class LargeStuff 
 Implementiert IDisposable 
 Private _Large als String () 
 
 'Ein seltsamer Code, der bedeutet, dass _Large jetzt mehrere Millionen lange Strings enthält. 
 
 Public Sub Dispose () Implementiert IDisposable.Dispose 
 _Large = Nothing 
 End Sub 

Mir ist klar, dass die Implementierung von Disposable nicht den aktuellen Richtlinien entspricht, aber Sie alle werden hoffentlich auf die Idee kommen.
Nun, wenn Dispose aufgerufen wird, wie viel Speicher wird freigegeben?

Antwort: Keine.
Durch Aufrufen von Dispose können nicht verwaltete Ressourcen freigegeben werden. Verwalteter Speicher kann NICHT zurückgefordert werden. Dies kann nur der GC. Das heißt nicht, dass das oben Genannte keine gute Idee ist. Dem obigen Muster zu folgen, ist in der Tat immer noch eine gute Idee. Nachdem Dispose ausgeführt wurde, kann der GC den von _Large verwendeten Speicher nicht mehr zurückfordern, obwohl die Instanz von LargeStuff möglicherweise noch im Gültigkeitsbereich ist. Die Zeichenfolgen in _Large befinden sich möglicherweise auch in Gen 0, aber die Instanz von LargeStuff ist möglicherweise Gen 2, sodass der Speicher früher wieder beansprucht wird.
Es hat keinen Sinn, einen Finalizer hinzuzufügen, um die oben gezeigte Dispose-Methode aufzurufen. Dadurch wird nur die erneute Beanspruchung des Speichers verzögert, damit der Finalizer ausgeführt werden kann.

6
pipTheGeek

Abgesehen von seiner primären Verwendung als Mittel zur Steuerung der Lebensdauer von Systemressourcen (vollständig abgedeckt durch die großartige Antwort von Ian , Kudos!), der IDisposable/using Combo kann auch verwendet werden, um den Statuswechsel von (kritischen) globalen Ressourcen zu erfassen : die Konsole , die Threads , der Prozess , ein beliebiges globales Objekt wie ein Anwendungsinstanz .

Ich habe einen Artikel über dieses Muster geschrieben: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

Es zeigt, wie Sie einige häufig verwendete globale Zustände in einem wiederverwendbaren und lesbaren Zustand schützen können. -) Art und Weise: Konsolenfarben , aktuelle Threadkultur , Excel-Anwendungsobjekteigenschaften ) ...

5
Pragmateek

Wenn überhaupt, würde ich erwarten, dass der Code weniger effizient ist, als wenn man ihn weglässt.

Das Aufrufen der Clear () -Methoden ist nicht erforderlich, und der GC würde das wahrscheinlich nicht tun, wenn der Dispose es nicht getan hätte ...

4
Arjan Einbu

Der gerechtfertigtste Anwendungsfall für die Entsorgung verwalteter Ressourcen ist die Vorbereitung des GC auf die Rückforderung von Ressourcen, die sonst niemals gesammelt würden.

Ein Paradebeispiel sind Zirkelverweise.

Es wird zwar empfohlen, Muster zu verwenden, die Zirkelverweise vermeiden. Wenn Sie jedoch (beispielsweise) ein untergeordnetes Objekt haben, das einen Verweis auf das übergeordnete Objekt enthält, kann dies die GC-Erfassung des übergeordneten Objekts stoppen, wenn Sie den Vorgang einfach abbrechen die Referenz und verlassen Sie sich auf GC - und wenn Sie einen Finalizer implementiert haben, wird er nie aufgerufen.

Die einzige Möglichkeit, dies zu umgehen, besteht darin, die Zirkelverweise manuell zu unterbrechen, indem Sie die übergeordneten Verweise für die untergeordneten Verweise auf Null setzen.

Das Implementieren von IDisposable für Eltern und Kinder ist der beste Weg, dies zu tun. Wenn Dispose für das übergeordnete Element aufgerufen wird, rufen Sie Dispose für alle untergeordneten Elemente auf, und setzen Sie in der untergeordneten Dispose-Methode die übergeordneten Verweise auf null.

2
controlbox

Ihr angegebenes Codebeispiel ist kein gutes Beispiel für die Verwendung von IDisposable. Löschen des Wörterbuchs normalerweise sollte nicht zur Methode Dispose wechseln. Wörterbuchelemente werden gelöscht und entsorgt, wenn sie den Gültigkeitsbereich verlassen. Die Implementierung von IDisposable ist erforderlich, um einige Speicher/Handler freizugeben, die auch dann nicht freigegeben/freigegeben werden, wenn sie außerhalb des Gültigkeitsbereichs liegen.

Das folgende Beispiel zeigt ein gutes Beispiel für ein IDisposable-Muster mit Code und Kommentaren.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}
2
CharithJ

Es gibt Dinge, die die Dispose() -Operation im Beispielcode bewirkt, die möglicherweise einen Effekt haben, der aufgrund eines normalen GC des MyCollection -Objekts nicht auftreten würde.

Wenn die Objekte, auf die von _theList oder _theDict verwiesen wird, von anderen Objekten referenziert werden, dann wird dieses List<> oder Dictionary<> Objekt nicht der Sammlung unterliegen, sondern plötzlich keinen Inhalt haben. Wenn keine Dispose () -Operation wie im Beispiel vorhanden wäre, würden diese Sammlungen weiterhin ihren Inhalt enthalten.

Wenn dies der Fall wäre, würde ich es natürlich als kaputtes Design bezeichnen - ich weise nur (pedantisch, nehme ich an) darauf hin, dass die Operation Dispose() möglicherweise nicht vollständig redundant ist, abhängig davon, ob es andere Verwendungen von gibt die List<> oder Dictionary<>, die im Fragment nicht angezeigt werden.

2
Michael Burr

Erste Definition. Für mich bedeutet nicht verwaltete Ressource eine Klasse, die IDisposable-Schnittstelle oder etwas implementiert, das mit der Verwendung von Aufrufen an dll erstellt wurde. GC weiß nicht, wie man mit solchen Objekten umgeht. Wenn class zum Beispiel nur Werttypen hat, betrachte ich diese Klasse nicht als Klasse mit nicht verwalteten Ressourcen. Für meinen Code folge ich den nächsten Übungen:

  1. Wenn die von mir erstellte Klasse einige nicht verwaltete Ressourcen verwendet, bedeutet dies, dass ich auch die IDisposable-Schnittstelle implementieren sollte, um den Speicher zu bereinigen.
  2. Reinigen Sie Objekte, sobald ich die Verwendung beendet habe.
  3. In meiner dispose-Methode durchlaufe ich alle IDisposable-Mitglieder der Klasse und rufe Dispose auf.
  4. Rufen Sie in meiner Dispose-Methode GC.SuppressFinalize (this) auf, um den Garbage Collector zu benachrichtigen, dass mein Objekt bereits bereinigt wurde. Ich mache es, weil das Aufrufen von GC eine teure Operation ist.
  5. Als zusätzliche Vorsichtsmaßnahme versuche ich mehrmals Dispose () aufzurufen.
  6. Irgendwann füge ich das private Mitglied _disposed hinzu und checke Methodenaufrufe ein, bei denen das Objekt bereinigt wurde. Und wenn es bereinigt wurde, dann generiere ObjectDisposedException
    Die folgende Vorlage zeigt, was ich in Worten als Codebeispiel beschrieben habe:
public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

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

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }
2
Yuriy Zaletskyy

Ein Problem bei den meisten Diskussionen über "nicht verwaltete Ressourcen" ist, dass sie den Begriff nicht wirklich definieren, aber anscheinend implizieren, dass er etwas mit nicht verwaltetem Code zu tun hat. Es ist zwar richtig, dass viele Arten von nicht verwalteten Ressourcen mit nicht verwaltetem Code in Verbindung stehen, es ist jedoch nicht hilfreich, sich solche nicht verwalteten Ressourcen vorzustellen.

Stattdessen sollte man erkennen, was alle verwalteten Ressourcen gemeinsam haben: Sie alle beinhalten ein Objekt, das ein externes "Ding" auffordert, etwas in seinem Namen zu tun, zum Nachteil einiger anderer "Dinge", und das andere Unternehmen, das dem zustimmt, bis auf weiteres. Wenn das Objekt spurlos verlassen und verschwinden würde, würde nichts jemals das äußere „Ding“ sagen, das es nicht mehr braucht, um sein Verhalten für das Objekt zu ändern, das es nicht mehr gibt. infolgedessen würde die Nützlichkeit des Dings dauerhaft verringert.

Eine nicht verwaltete Ressource stellt also eine Vereinbarung dar, dass ein externes "Ding" sein Verhalten für ein Objekt ändert, was die Nützlichkeit dieses externen "Dings" unbrauchbar beeinträchtigen würde, wenn das Objekt aufgegeben würde und nicht mehr existiert. Eine verwaltete Ressource ist ein Objekt, das der Nutznießer einer solchen Vereinbarung ist, das sich jedoch für den Erhalt einer Benachrichtigung angemeldet hat, wenn es aufgegeben wird, und das diese Benachrichtigung verwendet, um seine Angelegenheiten in Ordnung zu bringen, bevor es zerstört wird.

2
supercat

IDisposable eignet sich zum Abbestellen von Ereignissen.

2
Adam Speight

Ich sehe, dass sich viele Antworten verschoben haben, um über die Verwendung von IDisposable sowohl für verwaltete als auch für nicht verwaltete Ressourcen zu sprechen. Ich würde diesen Artikel als eine der besten Erklärungen vorschlagen, die ich für die tatsächliche Verwendung von IDisposable gefunden habe.

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

Für die eigentliche Frage; Wenn Sie IDisposable verwenden, um verwaltete Objekte zu bereinigen, die viel Speicherplatz beanspruchen, lautet die kurze Antwort nein . Der Grund dafür ist, dass Sie ein IDisposable, sobald Sie es entsorgt haben, aus dem Geltungsbereich entfernen sollten. Zu diesem Zeitpunkt liegen alle untergeordneten Objekte, auf die verwiesen wird, ebenfalls außerhalb des Bereichs und werden gesammelt.

Die einzige wirkliche Ausnahme wäre, wenn Sie viel Speicher in verwalteten Objekten gebunden haben und diesen Thread blockiert haben, bis ein Vorgang abgeschlossen ist. Wenn diese Objekte nach Abschluss dieses Aufrufs nicht mehr benötigt werden, kann der Garbage Collector sie möglicherweise früher erfassen, indem Sie diese Verweise auf null setzen. Dieses Szenario würde jedoch einen fehlerhaften Code darstellen, der überarbeitet werden muss - kein Anwendungsfall von IDisposable.

0
MikeJ