Ordnungsgemäße Verwendung der IDisposable-Schnittstelle


1657

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

Für mich bedeutet "nicht verwaltet" Dinge wie Datenbankverbindungen, Sockets, Fensterhandles usw. Aber ich habe Code gesehen, in dem die Dispose()Methode implementiert ist, um verwaltete Ressourcen freizugeben , was mir überflüssig erscheint, da sich der Garbage Collector darum kümmern sollte Dies für dich.

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 freien Speicher des Garbage Collectors MyCollectionschneller als normalerweise?

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


34
Ich mag die akzeptierte Antwort, weil sie Ihnen das richtige "Muster" für die Verwendung von IDisposable angibt, aber wie das OP in seiner Bearbeitung sagte, beantwortet es nicht seine beabsichtigte Frage. IDisposable ruft den GC nicht auf, sondern markiert ein Objekt lediglich als zerstörbar. Aber was ist der wirkliche Weg, um "jetzt" Speicher freizugeben, anstatt darauf zu warten, dass GC einschaltet? Ich denke, diese Frage verdient mehr Diskussion.
Punit Vora

40
IDisposablemarkiert nichts. Die DisposeMethode unternimmt das, was sie tun muss, um die von der Instanz verwendeten Ressourcen zu bereinigen. Dies hat nichts mit GC zu tun.
John Saunders

4
@John. Ich verstehe IDisposable. Und aus diesem Grund habe ich gesagt, dass die akzeptierte Antwort nicht die beabsichtigte Frage (und die nachfolgende Bearbeitung) des OP beantwortet, ob IDisposable bei der <i> Freigabe von Speicher </ i> hilfreich ist. Da dies IDisposablenichts mit der Freigabe von Speicher zu tun hat, sondern nur mit Ressourcen. Wie Sie bereits sagten, müssen die verwalteten Verweise überhaupt nicht auf null gesetzt werden, was OP in seinem Beispiel getan hat. Die richtige Antwort auf seine Frage lautet also: "Nein, es hilft nicht, Speicher schneller freizugeben. Tatsächlich hilft es überhaupt nicht, Speicher freizugeben, nur Ressourcen." Trotzdem danke für deine Eingabe.
Punit Vora

9
@desigeek: Wenn dies der Fall ist, sollten Sie nicht gesagt haben "IDisposable ruft den GC nicht auf, sondern markiert nur ein Objekt als zerstörbar"
John Saunders

5
@desigeek: Es gibt keine garantierte Möglichkeit, Speicher deterministisch freizugeben. Sie könnten GC.Collect () aufrufen, aber das ist eine höfliche Bitte, keine Forderung. Alle laufenden Threads müssen angehalten werden, damit die Speicherbereinigung fortgesetzt werden kann. Lesen Sie das Konzept der .NET-Sicherheitspunkte, wenn Sie weitere Informationen benötigen , z . B. msdn.microsoft.com/en-us/library/678ysw69(v=vs.110). aspx . Wenn ein Thread nicht angehalten werden kann, z. B. weil nicht verwalteter Code aufgerufen wird, führt GC.Collect () möglicherweise überhaupt nichts aus.
Concrete Gannet

Antworten:


2608

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 Bitmapund 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 Disposeerneut 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.Finalizesagt:

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.DisposeImplementierung bereit , um eine deterministische Freigabe von Ressourcen für Instanzen Ihrer Klasse sicherzustellen .

Dies ist die Tugend der DisposeBereinigung 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 .


12
Sie können es besser machen - Sie müssen GC.SuppressFinalize () in Dispose einen Aufruf hinzufügen.
Sockel

55
@ Daniel Earwicker: Es ist wahr. Microsoft würde es lieben, wenn Sie Win32 nicht mehr verwenden und sich an gut abstrahierbare, tragbare, geräteunabhängige .NET Framework-Aufrufe halten. Wenn Sie das darunter liegende Betriebssystem durchsuchen möchten; weil Sie glauben zu wissen, welches Betriebssystem ausgeführt wird: Sie nehmen Ihr Leben selbst in die Hand. Nicht jede .NET-App wird unter Windows oder auf einem Desktop ausgeführt.
Ian Boyd

34
Dies ist eine großartige Antwort, aber ich denke, es würde jedoch von einer endgültigen Codeliste für einen Standardfall und für einen Fall profitieren, in dem die Klasse von einer Basisklasse abgeleitet ist, die Dispose bereits implementiert. Wenn ich zB hier gelesen habe ( msdn.microsoft.com/en-us/library/aa720161%28v=vs.71%29.aspx ), bin ich auch verwirrt darüber, was ich tun soll, wenn ich von der Klasse ableite , die Dispose bereits implementiert ( Hey, ich bin neu in diesem Bereich.
Integra753

5
@ GregS und andere: Im Allgemeinen würde ich mich nicht darum kümmern, Verweise auf zu setzen null. Erstens bedeutet dies, dass Sie sie nicht erstellen können readonly, und zweitens müssen Sie sehr hässliche !=nullÜberprüfungen durchführen (wie im Beispielcode). Sie könnten eine Flagge haben disposed, aber es ist einfacher, sich nicht darum zu kümmern. Der .NET GC ist aggressiv genug, dass ein Verweis auf ein Feld xnicht mehr als "verwendet" gezählt wird, wenn er die x.Dispose()Linie passiert .
porges

7
Auf der zweiten Seite von Don Box 'Buch, die Sie erwähnt haben, verwendet er das Beispiel einer O (1) -Implementierung des Suchalgorithmus, dessen "Details als Übung für den Leser verbleiben". Ich lachte.
Wischen

65

IDisposablewird häufig verwendet, um die usingAnweisung 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.");
        }
    }
}

6
Ich persönlich mag das, aber es stimmt nicht wirklich mit den Richtlinien für das Framework-Design überein.
mqp

4
Ich würde es als richtiges Design betrachten, da es einfache deterministische Bereiche und Bereichskonstruktionen / -bereinigungen ermöglicht, insbesondere wenn es auf komplexe Weise mit Ausnahmebehandlungs-, Sperr- und nicht verwalteten Ressourcenverwendungsblöcken vermischt wird. Die Sprache bietet dies als erstklassiges Feature.
Yfeldblum

Es folgt nicht genau den in der FDG angegebenen Regeln, aber es ist sicherlich eine gültige Verwendung des Musters, da es erforderlich ist, um von der "using-Anweisung" verwendet zu werden.
Scott Dorman

2
Solange Log.Outdent nicht wirft, ist daran definitiv nichts auszusetzen.
Daniel Earwicker

1
Die verschiedenen Antworten auf Ist es missbräuchlich, IDisposable und "using" als Mittel zu verwenden, um "Scoped Behaviour" für die Ausnahmesicherheit zu erhalten? Gehen Sie etwas genauer darauf ein, warum verschiedene Leute diese Technik mögen / nicht mögen. Es ist etwas umstritten.
Brian

44

Der Zweck des Dispose-Musters besteht darin, einen Mechanismus zum Bereinigen sowohl verwalteter als auch nicht verwalteter Ressourcen bereitzustellen. Wann dies auftritt, hängt davon ab, wie die Dispose-Methode aufgerufen wird. In Ihrem Beispiel bewirkt die Verwendung von Dispose eigentlich nichts im Zusammenhang mit Dispose, da das Löschen einer Liste keine Auswirkungen auf die Entsorgung dieser 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 Methode, die hier am wichtigsten ist, 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, wenn Sie den GC einfach für die Bereinigung sorgen lassen, besteht darin, dass Sie keine wirkliche Kontrolle darüber haben, wann der GC einen Erfassungszyklus ausführt (Sie können GC.Collect () aufrufen, sollten dies aber wirklich nicht tun), damit die Ressourcen möglicherweise erhalten bleiben länger als nötig. Denken Sie daran, dass der Aufruf von Dispose () keinen Erfassungszyklus verursacht oder in irgendeiner Weise dazu führt, dass der GC das Objekt sammelt / freigibt. Es bietet lediglich die Möglichkeit, die verwendeten Ressourcen deterministischer zu bereinigen und dem GC mitzuteilen, dass diese Bereinigung bereits durchgeführt wurde.

Der springende Punkt bei IDisposable und dem Entsorgungsmuster besteht nicht darin, sofort Speicher freizugeben. Das einzige Mal, dass ein Aufruf von Dispose tatsächlich die Chance hat, sofort Speicher freizugeben, ist, wenn das Szenario disposing == false behandelt und nicht verwaltete Ressourcen manipuliert werden. Bei verwaltetem Code wird der Speicher erst dann wiederhergestellt, wenn der GC einen Erfassungszyklus ausführt, über den Sie wirklich keine Kontrolle haben (außer das 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, die "Bereinigung" zu erzwingen.


4
Haben Sie nicht vergessen, den Finalizer zu implementieren?
Budda

@ Buddha: Nein, er benutzt einen SafeHandle. Kein Destruktor erforderlich.
Henk Holterman

9
+1 zum Hinzufügen des Sicherheitsnetzes für mehrere Aufrufe von Dispose (). Die Spezifikation besagt, dass mehrere Anrufe sicher sein sollten. Zu viele Microsoft-Klassen können dies nicht implementieren, und die ObjectDisposedException ist ärgerlich.
Jesse Chisholm

5
Dispose (bool disposing) ist jedoch Ihre eigene Methode für Ihre SimpleCleanup-Klasse und wird vom Framework niemals aufgerufen. Da Sie es nur mit "true" als Parameter aufrufen, ist "disposing" niemals falsch. Ihr Code ist dem MSDN-Beispiel für IDisposable sehr ähnlich, es fehlt jedoch der Finalizer, wie @Budda hervorhob, von dem der Aufruf mit disposing = false kommen würde.
Jojo

19

Nach dem Aufruf 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. Daher sollte der Benutzer einfach alle Verweise auf das gesamte Objekt verwerfen (auf null setzen), und alle darin enthaltenen verwandten Objekte werden automatisch bereinigt.

Was die allgemeine Frage zu verwaltet / nicht verwaltet und die Diskussion in anderen Antworten betrifft, 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 eine andere Funktion, die Sie aufrufen können, um es wieder aus diesem Zustand zu bringen. Im typischen Beispiel könnte die erste eine Funktion sein, die ein Dateihandle zurückgibt, und die zweite könnte ein Aufruf von sein CloseHandle.

Aber - und das ist der Schlüssel - sie können jedes passende Funktionspaar sein. Einer baut einen Zustand auf, der andere reißt ihn ab. 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 der Speicher. Es gibt zwei Arten: den GC und den Stapel. Werttypen werden vom Stapel verwaltet (oder durch Anhängen einer Fahrt innerhalb von 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 sind möglicherweise threadsicher oder nicht.

Schauen Sie sich das Beispiel in der Frage der Justiz an. Änderungen am Einzug der Protokolldatei müssen perfekt verschachtelt sein, sonst geht alles schief. 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 Lebensdauern haben können, die sich in irgendeiner Weise überlappen. Das Beispiel einer Ressource für Gerechtigkeit darf also KEINEN Finalisierer haben! Es würde einfach niemandem helfen.

Für diese Art von Ressourcen können Sie sie einfach IDisposableohne Finalizer implementieren . Der Finalizer ist absolut optional - es muss sein. Dies wird in vielen Büchern beschönigt oder gar nicht erwähnt.

Sie müssen dann die usingAnweisung verwenden, um sicherzustellen, dass sie Disposeaufgerufen wird. Dies ist im Wesentlichen so, als würde man eine Fahrt mit dem Stapel ankuppeln (so wie der Finalizer für den GC ist, usingist er für den Stapel).

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 Zustände bevorzuge, die perfekt verschachtelt und nicht threadsicher sind (abgesehen von allem anderen erspart Ihnen das Vermeiden 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, an den zurückgerufen werden kann:

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 übergebene Lambda dient als Codeblock. Sie erstellen also Ihre eigene Kontrollstruktur, um denselben Zweck zu erfüllen using, außer dass Sie keine Gefahr mehr haben, dass der Anrufer es missbraucht. Es gibt keine Möglichkeit, die Ressource zu bereinigen.

Diese Technik ist weniger nützlich, wenn die Ressource überlappende Lebensdauern aufweist, da Sie dann Ressource A, dann Ressource B, dann Ressource A und später Ressource B erstellen möchten. Das können Sie nicht wenn Sie den Benutzer gezwungen haben, so perfekt zu verschachteln. Aber dann müssen Sie verwenden IDisposable(aber immer noch ohne Finalizer, es sei denn, Sie haben Threadsafety implementiert, die nicht kostenlos ist).


Betreff: "Nach dem Aufruf von Dispose sollten die Methoden eines Objekts nicht mehr aufgerufen werden." "Sollte" ist das operative Wort. Wenn asynchrone Aktionen ausstehen, können diese eingehen, nachdem Ihr Objekt entsorgt wurde. Auslösen einer ObjectDisposedException.
Jesse Chisholm

Ihre Antwort scheint die einzige andere als meine zu sein, die die Idee berührt, dass nicht verwaltete Ressourcen einen Zustand einschließen, den der GC nicht versteht. Ein Schlüsselaspekt einer nicht verwalteten Ressource ist jedoch, dass eine oder mehrere Entitäten, deren Status möglicherweise bereinigt werden muss, weiterhin existieren können, selbst wenn das Objekt, das die Ressource "besitzt", dies nicht tut. Wie gefällt dir meine Definition? Ziemlich ähnlich, aber ich denke, es macht die "Ressource" ein wenig nomenhafter (es ist die "Vereinbarung" des externen Objekts, sein Verhalten zu ändern, im Austausch für die Benachrichtigung, wenn seine Dienste nicht mehr benötigt werden)
supercat

@supercat - wenn Sie interessiert sind, schrieb ich den folgenden Beitrag ein paar Tage, nachdem ich die obige Antwort geschrieben hatte: smellegantcode.wordpress.com/2009/02/13/…
Daniel Earwicker

1
@DanielEarwicker: Interessanter Artikel, obwohl ich mir mindestens eine Art von nicht verwalteter Ressource vorstellen kann, die Sie nicht wirklich behandeln: Abonnements für Ereignisse von langlebigen Objekten. Ereignisabonnements sind fungibel, aber selbst wenn der Speicher unbegrenzt wäre, könnte die Nichtentsorgung kostspielig sein. Beispielsweise muss ein Enumerator für eine Sammlung, der Änderungen während der Enumeration ermöglicht, möglicherweise Aktualisierungsbenachrichtigungen aus der Sammlung abonnieren, und eine Sammlung kann im Laufe ihrer Lebensdauer viele Male aktualisiert werden. Wenn die Enumeratoren verlassen werden, ohne sich
abzumelden

1
Das Paar von Operationen enterund exitist der Kern meiner Meinung nach einer Ressource. Das Abonnieren / Abbestellen von Veranstaltungen sollte problemlos dazu passen. In Bezug auf die orthogonalen / fungiblen Eigenschaften ist es praktisch nicht von einem Speicherverlust zu unterscheiden. (Dies ist nicht überraschend, da Abonnement nur Objekte zu einer Liste hinzufügt.)
Daniel Earwicker

17

Szenarien, die ich mit IDisposable verwende: Nicht verwaltete Ressourcen bereinigen, Ereignisse abbestellen, Verbindungen schließen

Die Redewendung, die ich für die Implementierung von IDisposable verwende ( 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
}

Die vollständige Erklärung des Musters finden Sie unter msdn.microsoft.com/en-us/library/b1yfkh5e.aspx
LicenseQ

3
sollte niemals einen Finalizer enthalten, es sei denn, Sie haben nicht verwaltete Ressourcen. Selbst dann besteht die bevorzugte Implementierung darin, die nicht verwaltete Ressource in SafeHandle zu verpacken.
Dave Black

11

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

Antwort auf Ihre Bearbeitung: 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 es funktional identisch damit:

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 sofort freigeben müssen, rufen Sie an GC.Collect(). Hier gibt es jedoch keinen Grund, dies zu tun. Der Speicher wird freigegeben, wenn er benötigt wird.


2
Betreff: "Der Speicher wird freigegeben, wenn er benötigt wird." Sagen Sie lieber: "Wenn GC entscheidet, dass es gebraucht wird." Möglicherweise treten Probleme mit der Systemleistung auf, bevor GC entscheidet, dass Speicher wirklich benötigt wird. Jetzt freizugeben ist möglicherweise nicht unbedingt erforderlich, kann aber nützlich sein.
Jesse Chisholm

1
Es gibt einige Eckfälle, in denen das Aufheben von Referenzen innerhalb einer Sammlung die Speicherbereinigung der Elemente, auf die verwiesen wird, beschleunigen kann. Wenn beispielsweise ein großes Array erstellt und mit Verweisen auf kleinere neu erstellte Elemente gefüllt wird, dies jedoch nicht lange danach benötigt wird, kann das Verlassen des Arrays dazu führen, dass diese Elemente bis zum nächsten Level 2 GC beibehalten werden. Wenn Sie es zuerst auf Null setzen, können die Elemente für die nächste Stufe 0 oder Stufe 1 GC in Frage kommen. Natürlich ist es ohnehin schwierig, große kurzlebige Objekte auf dem Heap für große Objekte zu haben (ich mag das Design nicht), aber ...
Supercat

1
... solche Arrays auf Null zu setzen, bevor sie aufgegeben werden, verringert manchmal die GC-Auswirkung.
Supercat

11

Wenn MyCollectionsowieso Müll gesammelt wird, sollten Sie ihn nicht entsorgen müssen. Wenn Sie dies tun, wird die CPU nur mehr als nötig aufgewühlt, und möglicherweise wird sogar eine vorberechnete Analyse ungültig, die der Garbage Collector bereits durchgeführt hat.

Ich IDisposablemache Dinge wie sicherzustellen, dass Threads korrekt entsorgt werden, zusammen mit nicht verwalteten Ressourcen.

BEARBEITEN Als Antwort auf Scotts Kommentar:

Die GC-Leistungsmetriken werden nur dann beeinflusst, wenn ein Aufruf von [sic] GC.Collect () erfolgt. "

Konzeptionell behält der GC eine Ansicht des Objektreferenzdiagramms und aller Verweise darauf aus den Stapelrahmen von Threads bei. Dieser Heap kann sehr groß sein und viele Speicherseiten umfassen. Als Optimierung speichert der GC die Analyse von Seiten zwischen, deren Änderung wahrscheinlich nicht sehr häufig erfolgt, 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 Dinge auf der Seite, dies ist jedoch in Gen1 und Gen2 weniger wahrscheinlich. Anekdotisch waren diese Hooks in Mac OS X für das Team, das den GC auf Mac portierte, nicht verfügbar, damit das Silverlight-Plug-In auf dieser Plattform funktioniert.

Ein weiterer Punkt gegen 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 seit einiger Zeit ausgeführt wird. Es besteht die Möglichkeit, dass viele der Speicherseiten dieses Prozesses auf die Festplatte ausgelagert wurden. Zumindest befinden sie sich nicht mehr im L1- oder L2-Cache. In einer solchen Situation macht es keinen Sinn, dass eine entladene Anwendung all diese Daten und Codepages zurück in den Speicher tauscht, um Ressourcen freizugeben, die vom Betriebssystem ohnehin 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 Nicht-Hintergrund-Threads am Leben erhalten. Andernfalls bleibt der Prozess am Leben.

Jetzt gibt es während der normalen Ausführung kurzlebige Ressourcen, die korrekt bereinigt werden müssen (wie @fezmonkey auf Datenbankverbindungen, Sockets und Fensterhandles hinweist ), um nicht verwaltete Speicherlecks zu vermeiden. Dies sind die Dinge, die entsorgt werden müssen. Wenn Sie eine Klasse erstellen, die einen Thread besitzt (und mit eigenen meine ich, dass sie ihn erstellt hat und daher dafür verantwortlich ist, dass er zumindest durch meinen Codierungsstil stoppt), muss diese Klasse IDisposableden Thread höchstwahrscheinlich während des Threads implementieren und abreißen Dispose.

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


Calling Dispose ist absolut gültig, legal und wird empfohlen. Objekte, die IDisposable implementieren, tun dies normalerweise aus einem bestimmten Grund. Die GC-Leistungsmetriken sind nur betroffen, wenn ein Aufruf von GC.Collect () erfolgt.
Scott Dorman

Für viele .net-Klassen ist die Entsorgung "etwas" optional, was bedeutet, dass das Verlassen von Instanzen "normalerweise" keine Probleme verursacht, solange man nicht verrückt wird, neue Instanzen zu erstellen und sie aufzugeben. Beispielsweise scheint der vom Compiler generierte Code für Steuerelemente Schriftarten zu erstellen, wenn die Steuerelemente instanziiert werden, und sie zu verlassen, wenn die Formulare entsorgt werden. Wenn Tausende von Steuerelementen erstellt und entsorgt werden, können Tausende von GDI-Handles gebunden werden. In den meisten Fällen werden Steuerelemente jedoch nicht so häufig erstellt und zerstört. Trotzdem sollte man versuchen, eine solche Aufgabe zu vermeiden.
Supercat

1
Ich vermute, dass bei Schriftarten das Problem darin besteht, dass Microsoft nie wirklich definiert hat, welche Entität für die Entsorgung des einem Steuerelement zugewiesenen "Schriftarten" -Objekts verantwortlich ist. In einigen Fällen kann ein Steuerelement eine Schriftart für ein längerlebiges Objekt freigeben. Daher wäre es schlecht, wenn das Steuerelement die Schriftart entsorgen würde. In anderen Fällen wird eine Schriftart einem Steuerelement zugewiesen und nirgendwo anders. Wenn das Steuerelement sie also nicht entsorgt, wird dies niemand tun. Im Übrigen hätte diese Schwierigkeit mit Schriftarten vermieden werden können, wenn es eine separate nicht wegwerfbare FontTemplate-Klasse gegeben hätte, da Steuerelemente das GDI-Handle ihrer Schriftart nicht zu verwenden scheinen.
Supercat

Zum Thema optionale Dispose()Aufrufe siehe: stackoverflow.com/questions/913228/…
RJ Cuthbertson

7

In dem von Ihnen geposteten Beispiel wird der Speicher immer noch nicht freigegeben. Der gesamte Speicher wird durch Müll gesammelt, es kann jedoch möglich sein, dass der Speicher in einer früheren Generation gesammelt wird . Sie müssten einige Tests durchführen, um sicherzugehen.


Die Framework Design Guidelines sind Richtlinien und keine Regeln. Sie sagen Ihnen, wofür die Benutzeroberfläche in erster Linie gedacht ist, wann sie verwendet werden soll, wie sie verwendet werden soll und wann sie nicht verwendet werden soll.

Ich habe einmal Code gelesen, der ein einfaches RollBack () bei einem Fehler mit IDisposable war. Die unten stehende MiniTx-Klasse prüft ein Flag auf Dispose (), und wenn der CommitAufruf nie erfolgt ist, ruft Rollbacker sich selbst auf. Es wurde eine Indirektionsebene hinzugefügt, die das Aufrufen und Verwalten des aufrufenden Codes erheblich erleichtert. 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- / Protokollierungscode dasselbe tun. In diesem Fall stoppte die Dispose () -Methode den Timer und protokollierte, dass der Block beendet wurde.

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

Hier sind einige konkrete Beispiele, die keine nicht verwaltete Ressourcenbereinigung durchführen, aber IDisposable erfolgreich zum Erstellen von sauberem Code verwenden.


Schauen Sie sich das Beispiel von @Daniel Earwicker mit Funktionen höherer Ordnung an. Für Benchmarking, Timing, Protokollierung usw. scheint es viel einfacher zu sein.
Aluan Haddad


6

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

Public Class LargeStuff
  Implementiert IDisposable
  Private _Large as string ()

  'Ein seltsamer Code, der bedeutet, dass _Large jetzt mehrere Millionen lange Zeichenfolgen enthält.

  Public Sub Dispose () Implementiert IDisposable.Dispose
    _Large = Nichts
  End Sub

Mir ist klar, dass die Disposable-Implementierung nicht den aktuellen Richtlinien entspricht, aber hoffentlich haben Sie alle die Idee.
Wenn Dispose aufgerufen wird, wie viel Speicher wird freigegeben?

Antwort: Keine. Es macht keinen Sinn, einen Finaliser hinzuzufügen, um die oben gezeigte Dispose-Methode aufzurufen. Dies verzögert lediglich die erneute Inanspruchnahme des Speichers, damit der Finalisierer ausgeführt werden kann.
Durch Aufrufen von Dispose können nicht verwaltete Ressourcen freigegeben werden. Der verwaltete Speicher kann NICHT zurückgefordert werden. Dies kann nur der GC. Das heißt nicht, dass das oben Genannte keine gute Idee ist. Das Befolgen des obigen Musters ist in der Tat immer noch eine gute Idee. Sobald Dispose ausgeführt wurde, kann der GC den von _Large verwendeten Speicher nicht mehr beanspruchen, 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 in Gen 2, sodass der Speicher wieder früher beansprucht wird.


1
Wenn eine Instanz von LargeStufflange genug existiert hat, um es bis zur 2. Generation zu schaffen, und wenn sie _Largeeinen Verweis auf eine neu erstellte Zeichenfolge enthält, die sich in der Generation 0 befindet, wird die Zeichenfolge von verwiesen , wenn die Instanz von LargeStuffohne Nullung abgebrochen _Largewird _Largewird bis zur nächsten Gen2-Kollektion aufbewahrt. Durch Nullstellen wird _Largeder String möglicherweise bei der nächsten Gen0-Sammlung entfernt. In den meisten Fällen ist das Aufheben von Referenzen nicht hilfreich, aber es gibt Fälle, in denen dies einen gewissen Nutzen bringen kann.
Supercat

5

Abgesehen von seiner primären Verwendung zur Steuerung der Lebensdauer von Systemressourcen (vollständig abgedeckt durch die großartige Antwort von Ian , Kudos!) Kann die Kombination IDisposable / using auch verwendet werden , um die Statusänderung von (kritischen) globalen Ressourcen zu erfassen : die Konsole , die Threads , der Prozess , jedes globale Objekt wie eine 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 einen häufig verwendeten globalen Status wiederverwendbar und lesbar schützen können : Konsolenfarben , aktuelle Thread-Kultur , Eigenschaften von Excel-Anwendungsobjekten ...


4

Wenn überhaupt, würde ich erwarten, dass der Code weniger effizient ist als beim Auslassen.

Das Aufrufen der Clear () -Methoden ist nicht erforderlich, und der GC würde dies wahrscheinlich nicht tun, wenn Dispose dies nicht tun würde ...


2

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

Wenn die referenzierten Objekte _theListoder _theDictdurch andere Objekte bezeichnet werden, dann ist das List<>oder Dictionary<>Objekt nicht Gegenstand Sammlung sein , sondern plötzlich keinen Inhalt haben. Wenn es keine Dispose () -Operation wie im Beispiel gäbe, würden diese Sammlungen immer noch ihren Inhalt enthalten.

Wenn dies die Situation wäre, würde ich es natürlich als defektes Design bezeichnen - ich weise nur (pedantisch, nehme ich an) darauf hin, dass der Dispose()Vorgang möglicherweise nicht vollständig redundant ist, je nachdem, ob es andere Verwendungszwecke gibt List<>oder Dictionary<>nicht im Fragment gezeigt.


Da es sich um private Bereiche handelt, ist es fair anzunehmen, dass das OP keine Hinweise auf sie gibt.
mqp

1) Das Codefragment ist nur ein Beispielcode, daher möchte ich nur darauf hinweisen, dass es einen Nebeneffekt geben kann, der leicht zu übersehen ist. 2) Private Felder sind oft das Ziel einer Getter-Eigenschaft / -Methode - möglicherweise zu viel (Getter / Setter werden von einigen Leuten als ein bisschen anti-Muster angesehen).
Michael Burr

2

Ein Problem bei den meisten Diskussionen über "nicht verwaltete Ressourcen" besteht darin, dass sie den Begriff nicht wirklich definieren, aber zu implizieren scheinen, dass er etwas mit nicht verwaltetem Code zu tun hat. Zwar sind viele Arten von nicht verwalteten Ressourcen mit nicht verwaltetem Code verbunden, doch ist es nicht hilfreich, an nicht verwaltete Ressourcen in solchen Begriffen zu denken.

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 die andere Entität stimmt zu, dies bis zu tun auf weiteres. Wenn das Objekt verlassen würde und spurlos verschwinden würde, würde nichts dem äußeren „Ding“ jemals sagen, dass es sein Verhalten für das nicht mehr existierende Objekt nicht mehr ändern muss; folglich würde die Nützlichkeit des Dings dauerhaft verringert.

Eine nicht verwaltete Ressource stellt also eine Vereinbarung eines externen "Dings" dar, sein Verhalten im Namen eines Objekts zu ändern, was die Nützlichkeit dieses externen "Dings" nutzlos beeinträchtigen würde, wenn das Objekt verlassen würde und nicht mehr existiert. Eine verwaltete Ressource ist ein Objekt, das von einer solchen Vereinbarung profitiert, sich jedoch für den Erhalt einer Benachrichtigung angemeldet hat, wenn sie aufgegeben wird, und das diese Benachrichtigung verwendet, um seine Angelegenheiten in Ordnung zu bringen, bevor sie zerstört wird.


Nun, IMO, die Definition eines nicht verwalteten Objekts ist klar. jedes Nicht-GC-Objekt .
Eonil

1
@Eonil: Nicht verwaltetes Objekt! = Nicht verwaltete Ressource. Dinge wie Ereignisse können vollständig mit verwalteten Objekten implementiert werden, stellen jedoch immer noch nicht verwaltete Ressourcen dar, da der GC - zumindest bei kurzlebigen Objekten, die Ereignisse langlebiger Objekte abonnieren - nichts darüber weiß, wie sie bereinigt werden sollen .
Superkatze


2

Erste Definition. Für mich bedeutet nicht verwaltete Ressource eine Klasse, die eine IDisposable-Schnittstelle oder etwas implementiert, das unter Verwendung von Aufrufen der DLL erstellt wurde. GC weiß nicht, wie er mit solchen Objekten umgehen soll. Wenn die Klasse 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 eine IDisposable-Schnittstelle implementieren sollte, um den Speicher zu bereinigen.
  2. Reinigen Sie Objekte, sobald ich damit fertig bin.
  3. In meiner Dispose-Methode iteriere ich über 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, Dispose () mehrmals 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, generieren Sie 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;
        }
    }

1
"Für mich bedeutet eine nicht verwaltete Ressource eine Klasse, die eine IDisposable-Schnittstelle implementiert oder etwas, das unter Verwendung von Aufrufen der DLL erstellt wurde." Sie sagen also, dass jeder Typ, der is IDisposableselbst als nicht verwaltete Ressource betrachtet werden sollte? Das scheint nicht richtig zu sein. Auch wenn der Implementierungstyp ein reiner Werttyp ist, scheinen Sie darauf hinzuweisen, dass er nicht entsorgt werden muss. Das scheint auch falsch.
Aluan Haddad

Jeder urteilt nach sich selbst. Ich mag es nicht, meinem Code etwas hinzuzufügen, nur um es hinzuzufügen. Wenn ich IDisposable hinzufüge, bedeutet dies, dass ich eine Funktionalität erstellt habe, die GC nicht verwalten kann, oder ich nehme an, dass sie ihre Lebensdauer nicht ordnungsgemäß verwalten kann.
Yuriy Zaletskyy

2

Ihr angegebenes Codebeispiel ist kein gutes Beispiel für die IDisposableVerwendung. Das Löschen von Wörterbüchern sollte normalerweise nicht zur DisposeMethode gehen. Wörterbuchelemente werden gelöscht und entsorgt, wenn der Gültigkeitsbereich überschritten wird.IDisposableDie Implementierung 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.
    }
}

1

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.

Während es empfehlenswert ist, Muster zu verwenden, die Zirkelverweise vermeiden, kann dies die GC-Erfassung des übergeordneten Objekts stoppen, wenn Sie (z. B.) ein 'untergeordnetes' Objekt haben, das einen Verweis auf sein 'übergeordnetes' Objekt hat, wenn Sie es einfach abbrechen die Referenz und verlassen Sie sich auf GC - plus, wenn Sie einen Finalizer implementiert haben, wird es nie aufgerufen.

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

Die Implementierung 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.


4
Zum größten Teil funktioniert der GC nicht durch die Identifizierung toter Objekte, sondern durch die Identifizierung lebender Objekte. Nachdem jeder GC-Zyklus für jedes Objekt, das sich zur Finalisierung registriert hat, auf dem großen Objekthaufen gespeichert ist oder das Ziel eines Live ist WeakReference, überprüft das System ein Flag, das angibt, dass im letzten GC-Zyklus eine Live-Root-Referenz gefunden wurde und fügt das Objekt entweder einer Warteschlange von Objekten hinzu, die sofort finalisiert werden müssen, gibt das Objekt aus dem großen Objekthaufen frei oder macht die schwache Referenz ungültig. Zirkuläre Refs halten Objekte nicht am Leben, wenn keine anderen Refs vorhanden sind.
Supercat

1

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

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, sobald Sie ein IDisposable entsorgen, es aus dem Geltungsbereich entfernen sollten. Zu diesem Zeitpunkt sind auch alle referenzierten untergeordneten Objekte außerhalb des Gültigkeitsbereichs und werden gesammelt.

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


1
Ich habe nicht verstanden, warum jemand -1 auf Ihre Antwort gesetzt hat
Sebastian Oscar Lopez
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.