richtig - idisposable implementation c#




Richtige Verwendung der IDisposable-Schnittstelle (12)

Der Zweck des Dispose-Musters besteht darin, einen Mechanismus zum Bereinigen von verwalteten und nicht verwalteten Ressourcen bereitzustellen, und wenn dies auftritt, hängt es davon ab, wie die Dispose-Methode aufgerufen wird. In Ihrem Beispiel führt die Verwendung von Dispose nicht zu einer Entsorgung, da das Löschen einer Liste keine Auswirkungen auf die entsorgte Sammlung hat. Ebenso haben die Aufrufe, die Variablen auf null zu setzen, keine Auswirkungen auf den GC.

In diesem article weitere Informationen zum Implementieren des Dispose-Musters. Es sieht jedoch im Wesentlichen wie folgt 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 das Dispose (bool), das eigentlich unter zwei verschiedenen Umständen läuft:

  • disposing == true: Die Methode wurde direkt oder indirekt von einem Benutzercode aufgerufen. Verwaltete und nicht verwaltete Ressourcen können entsorgt werden.
  • disposing == false: Die Methode wurde von der Laufzeit aus dem Finalizer aufgerufen, und Sie sollten nicht auf andere Objekte verweisen. Nur nicht verwaltete Ressourcen können entsorgt werden.

Das Problem, den GC einfach die Säuberung durchführen zu lassen, besteht darin, dass Sie keine Kontrolle darüber haben, wann der GC einen Erfassungszyklus ausführt (Sie können GC.Collect () aufrufen, aber das sollten Sie wirklich nicht) um länger als nötig. Denken Sie daran, dass der Aufruf von Dispose () keinen Sammlungszyklus auslöst oder in irgendeiner Weise dazu führt, dass der GC das Objekt sammelt / freigibt; Es bietet einfach die Möglichkeit, die verwendeten Ressourcen deterministischer zu bereinigen und dem GC mitzuteilen, dass diese Bereinigung bereits durchgeführt wurde.

Der Hauptpunkt von IDisposable und dem Dispose-Muster besteht nicht darin, Speicher sofort freizugeben. Der einzige Zeitpunkt, an dem ein Dispose-Aufruf tatsächlich eine Chance hat, Speicher sofort freizugeben, ist, wenn er das Szenario disposing == false verarbeitet und nicht verwaltete Ressourcen manipuliert. Bei verwaltetem Code wird der Speicher erst wieder freigegeben, wenn der GC einen Erfassungszyklus ausführt, über den Sie wirklich keine Kontrolle haben (außer dem Aufruf von GC.Collect (), was ich bereits erwähnt habe, ist keine gute Idee).

Ihr Szenario ist nicht wirklich gültig, da Zeichenfolgen in .NET keine nicht verknüpften Ressourcen verwenden und IDisposable nicht implementieren. Es gibt keine Möglichkeit, sie zu zwingen, "bereinigt" zu werden.

Ich weiß aus dem Lesen der MSDN-Dokumentation, dass die "primäre" Verwendung der IDisposable Schnittstelle nicht verwaltete Ressourcen bereinigen soll.

"Unmanaged" bedeutet für mich Dinge wie Datenbankverbindungen, Sockets, Fenstergriffe usw. Aber ich habe Code gesehen, bei dem die Dispose() -Methode implementiert wurde, um verwaltete Ressourcen freizugeben, was für mich überflüssig scheint, da der Garbage Collector es sollte kümmere mich darum für dich.

Beispielsweise:

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 von Garbage Collector, der von MyCollection verwendet wird, schneller als normalerweise?

Bearbeiten : Bisher haben einige gute Beispiele für die Verwendung von IDisposable zur Bereinigung nicht verwalteter Ressourcen wie Datenbankverbindungen und Bitmaps gepostet. Aber nehmen wir an, dass _theList im obigen Code eine Million Strings enthielt und Sie diesen Speicher jetzt _theList wollten, anstatt auf den Garbage Collector zu warten. Würde der obige Code das erreichen?


Der Zweck von Dispose besteht darin , nicht verwaltete Ressourcen freizugeben. Es muss irgendwann gemacht werden, sonst werden sie nie wieder sauber gemacht. Der Garbage Collector kann DeleteHandle() für eine Variable vom Typ IntPtr nicht IntPtr , er weiß nicht, ob er DeleteHandle() aufrufen DeleteHandle() .

Hinweis : Was ist eine nicht verwaltete Ressource ? Wenn Sie es in Microsoft .NET Framework gefunden haben: Es ist verwaltet. Wenn Sie MSDN selbst durchsuchen, ist es nicht verwaltet. Alles, was Sie mit P / Invoke aufgerufen haben, um außerhalb der netten, bequemen Welt von allem zu kommen, das Ihnen in .NET Framework zur Verfügung steht, ist nicht verwaltet - und Sie sind jetzt dafür verantwortlich, es zu bereinigen.

Das Objekt, das Sie erstellt haben, muss eine Methode offenlegen, die von der Außenwelt aufgerufen werden kann, um nicht verwaltete Ressourcen zu bereinigen. Die Methode kann wie immer benannt werden:

public void Cleanup()

public void Shutdown()

Stattdessen gibt es einen standardisierten Namen für diese Methode:

public void Dispose()

Es wurde sogar eine Schnittstelle erstellt, IDisposable , die genau diese eine Methode hat:

public interface IDisposable
{
   void Dispose()
}

Sie veranlassen also, dass Ihr Objekt die IDisposable Schnittstelle zur Verfügung stellt, und auf diese Weise versprechen Sie, dass Sie diese einzige Methode zum Bereinigen 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 eine System.Drawing.Bitmap 250MB (dh die von .NET verwaltete Bitmap-Klasse) als eine Art Framebuffer zugewiesen hat? Sicher, dies ist ein verwaltetes .NET-Objekt, und der Garbage Collector wird es befreien. Aber willst du wirklich 250MB Speicher lassen, die nur da sitzen - und darauf warten, dass der Müllsammler irgendwann kommt und es befreit? Was passiert, wenn eine offene Datenbankverbindung besteht ? Sicherlich wollen wir nicht, dass diese Verbindung offen steht und darauf warten, dass der GC das Objekt finalisiert.

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

So, jetzt werden wir:

  • loswerden von nicht verwalteten Ressourcen (weil wir müssen), und
  • Verwaltete Ressourcen loswerden (weil wir hilfreich sein wollen)

Lassen Sie uns also unsere Dispose() -Methode aktualisieren, um diese verwalteten Objekte loszuwerden:

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, wenn die Person vergessen hat , Dispose() für Ihr Objekt aufzurufen? Dann würden sie einige nicht verwaltete Ressourcen verlieren !

Hinweis: Sie werden keine verwalteten Ressourcen verlieren, weil der Garbage Collector schließlich in einem Hintergrundthread ausgeführt wird, und den mit nicht verwendeten Objekten verbundenen Speicher freigeben. Dies schließt Ihr Objekt und alle von Ihnen verwendeten verwalteten Objekte ein (z. B. die Bitmap und die DbConnection ).

Wenn die Person vergessen hat, Dispose() anzurufen, können wir ihren Speck noch retten! Wir haben immer noch eine Möglichkeit, es für sie zu nennen: wenn der Müllsammler endlich dazu kommt, unser Objekt zu befreien (dh zu finalisieren).

Hinweis: Der Garbage Collector wird schließlich alle verwalteten Objekte freigeben. Wenn dies der Fall ist, wird die Finalize Methode für das Objekt Finalize . Der GC weiß nicht oder interessiert sich nicht für Ihre Dispose- Methode. Das war nur ein Name, den wir für eine Methode gewählt haben, die wir aufrufen, wenn wir unmanaged Zeug loswerden wollen.

Die Zerstörung unseres Objekts durch den Müllsammler ist die perfekte Zeit, um diese lästigen nicht verwalteten Ressourcen zu befreien. Dazu überschreiben wir die Finalize() -Methode.

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

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

Aber es gibt einen Fehler in diesem Code. Sie sehen, der Garbage Collector wird auf einem Hintergrundthread ausgeführt . 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 loswerden 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() dass es keine verwalteten Ressourcen berührt (weil sie möglicherweise nicht mehr da sind), während weiterhin nicht verwaltete Ressourcen freigegeben werden.

Das Standardmuster dafür ist, dass Finalize() und Dispose() beide eine dritte (!) Methode aufrufen; Hier übergeben Sie ein boolesches Sprichwort, wenn Sie es von Dispose() aus aufrufen (im Gegensatz zu Finalize() ), was bedeutet, dass es sicher ist, verwaltete Ressourcen freizugeben.

Diese interne Methode könnte einen beliebigen Namen wie "CoreDispose" oder "MyInternalDispose" erhalten, aber es ist Tradition, sie Dispose(Boolean) zu nennen:

protected void Dispose(Boolean disposing)

Aber ein hilfreicher Parametername könnte 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 zu:

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

und dein Finalizer für:

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

Hinweis : Wenn Ihr Objekt von einem Objekt Dispose , das Dispose implementiert, vergessen Sie nicht, die Basis- Dispose-Methode aufzurufen, wenn Sie Dispose überschreiben:

public 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. Später, wenn der Garbage Collector vorbeikommt und Finalize aufruft, ruft er erneut Dispose auf.

Dies ist nicht nur verschwenderisch, sondern wenn Ihr Objekt über Junk-Verweise auf Objekte verfügt, die Sie bereits beim letzten Aufruf von Dispose() entsorgt haben, versuchen Sie, sie erneut zu entfernen.

Sie werden feststellen, dass ich in meinem Code darauf geachtet habe, Verweise auf Objekte zu entfernen, die ich entsorgt habe. Daher versuche ich nicht, Dispose für einen Verweis auf ein Junk-Objekt aufzurufen. Aber das hat einen kleinen Fehler nicht verhindert.

Wenn der Benutzer Dispose() aufruft: Der Handle CursorFileBitmapIconServiceHandle ist zerstört. Später, wenn der Garbage Collector ausgeführt wird, wird er versuchen, denselben Handle erneut zu zerstören.

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

Die Art und Weise, wie Sie das Problem beheben, ist, dass der Garbage Collector nicht mit der Fertigstellung des Objekts befasst werden muss - seine Ressourcen wurden bereits bereinigt und es ist keine weitere Arbeit erforderlich. Dazu rufen Sie GC.SuppressFinalize() in der Dispose() -Methode 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, dass der GC den Finalizer laufen lässt - alles ist erledigt.

Konnte ich Finalize nicht zum Bereinigen nicht verwalteter Ressourcen verwenden?

Die Dokumentation für Object.Finalize sagt:

Mit der Finalize-Methode werden Bereinigungsvorgänge für nicht verwaltete Ressourcen ausgeführt, die vom aktuellen Objekt vor der Zerstörung des Objekts gehalten werden.

Aber die MSDN-Dokumentation sagt auch für IDisposable.Dispose :

Führt anwendungsdefinierte Aufgaben im Zusammenhang mit dem Freigeben, Freigeben oder Zurücksetzen nicht verwalteter Ressourcen aus.

Also was ist es? Welches ist der Ort für mich, um nicht verwaltete Ressourcen zu bereinigen? Die Antwort ist:

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

Sie könnten Ihre nicht verwaltete Bereinigung im Finalizer platzieren:

~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 systemeigenen Ressourcen werden beibehalten, bis der Garbage Collector schließlich ausgeführt wird . Dann wird es Ihre Finalizer-Methode aufrufen; unmanaged Ressourcen bereinigen. Die Dokumentation von Object.Finalize weist darauf hin:

Der genaue Zeitpunkt, zu dem der Finalizer ausgeführt wird, ist nicht definiert. Um eine deterministische Freigabe von Ressourcen für Instanzen Ihrer Klasse sicherzustellen, implementieren Sie eine Close- Methode oder stellen Sie eine IDisposable.Dispose Implementierung IDisposable.Dispose .

Dies ist der Vorteil von Dispose zum Bereinigen nicht verwalteter Ressourcen. Sie erfahren und kontrollieren, wenn die nicht verwaltete Ressource bereinigt wird. Ihre Zerstörung ist "deterministisch" .

Um Ihre ursprüngliche Frage zu beantworten: Warum nicht jetzt Speicher freigeben und nicht, wenn der GC entscheidet, es zu tun? Ich habe eine Gesichtserkennungssoftware, die jetzt 530 MB interne Bilder loswerden muss, da sie nicht mehr benötigt werden. Wenn nicht, läuft die Maschine zum Stillstand.

Bonuslesen

Für jeden, der den Stil dieser Antwort mag (erklärt das Warum , so wie das offensichtlich wird), schlage ich vor, dass Sie Kapitel 1 von Don Box's Essential COM lesen:

Auf 35 Seiten erklärt er die Probleme der Verwendung von binären Objekten und erfindet COM vor Ihren Augen. Sobald Sie das Warum von COM erkennen, sind die verbleibenden 300 Seiten offensichtlich, und nur Details der Implementierung von Microsoft.

Ich denke, jeder Programmierer, der sich jemals mit Objekten oder COM beschäftigt hat, sollte zumindest das erste Kapitel lesen. Es ist die beste Erklärung von allem überhaupt.

Extra Bonus Lesen

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

Es ist daher sehr schwierig, einen korrekten Finalizer zu schreiben, und der beste Rat, den ich Ihnen geben kann, ist, es nicht zu versuchen .


Szenarien, die ich IDisposable verwende: bereinigen nicht verwaltete Ressourcen, Abmeldung für Ereignisse, Schließen von Verbindungen

Das Idiom, das ich zur Implementierung von IDisposable ( nicht threadsafe ) verwende:

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
}

Wenn MyCollection trotzdem als Garbage MyCollection erfasst wird, sollten Sie sie nicht MyCollection . Dadurch wird die CPU mehr als nötig umgestellt und möglicherweise sogar einige vorberechnete Analysen ungültig gemacht, die der Garbage Collector bereits durchgeführt hat.

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

EDIT Als Antwort auf Scotts Kommentar:

Das einzige Mal, wenn die GC-Leistungsmetriken betroffen sind, ist ein Aufruf der [sic] GC.Collect () -Methode.

Vom Konzept her behält der GC eine Ansicht des Objektreferenzgraphen und alle Verweise darauf von den Stapelrahmen von Fäden bei. Dieser Heap kann recht groß sein und viele Seiten Speicher umfassen. Als eine Optimierung zwischenspeichert der GC seine Analyse von Seiten, bei denen es 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. Er weiß also, dass die Seite schmutzig ist und erfordert einen erneuten Scan. Wenn die Sammlung in Gen0 ist, ist es wahrscheinlich, dass sich auch andere Dinge auf der Seite ändern, aber dies ist in Gen1 und Gen2 weniger wahrscheinlich. Anekdotenweise waren diese Hooks in Mac OS X für das Team, das den GC auf Mac portiert hatte, nicht verfügbar, um das Silverlight-Plug-in auf dieser Plattform zum Laufen zu bringen.

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 seit einiger Zeit läuft. Wahrscheinlich wurden viele Speicherseiten dieses Prozesses auf die Festplatte ausgelagert. Zumindest sind sie nicht mehr in L1 oder L2-Cache. In einer solchen Situation ist es für eine Anwendung, die entladen wird, keinen Sinn, alle diese Daten und Codepages wieder in den Speicher zu tauschen, um Ressourcen freizugeben, die vom Betriebssystem sowieso freigegeben werden, wenn der Prozess beendet wird. Dies gilt für verwaltete und sogar bestimmte nicht verwaltete Ressourcen. Nur Ressourcen, die Nicht-Hintergrund-Threads am Leben erhalten, müssen entsorgt werden, andernfalls bleibt der Prozess am Leben.

Nun, während der normalen Ausführung gibt es ephemere Ressourcen, die korrekt bereinigt werden müssen (wie @fezmonkey Datenbankverbindungen, Sockets, Fensterhandles anzeigt), um nicht gemanagte Speicherlecks zu vermeiden. Dies sind die Arten von Dingen, die entsorgt werden müssen. If you create some class that owns a thread (and by owns I mean that it created it and therefore is responsible for ensuring it stops, at least by my coding style), then that class most likely must implement IDisposable and tear down the thread during Dispose .

The .NET framework uses the IDisposable interface as a signal, even warning, to developers that the this class must be disposed. I can't think of any types in the framework that implement IDisposable (excluding explicit interface implementations) where disposal is optional.


IDisposable wird häufig verwendet, um die using Anweisung auszunutzen und einen einfachen Weg 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.");
        }
    }
}

Apart from its primary use as a way to control the lifetime of system resources (completely covered by the awesome answer of Ian , kudos!), the IDisposable/using combo can also be used to scope the state change of (critical) global resources : the console , the threads , the process , any global object like an application instance .

I've written an article about this pattern: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

It illustrates how you can protect some often used global state in a reusable and readable manner: console colors , current thread culture , Excel application object properties ...


I won't repeat the usual stuff about Using or freeing un-managed resources, that has all been covered. But I would like to point out what seems a common misconception.
Given the following code

Public Class LargeStuff
  Implements IDisposable
  Private _Large as string()

  'Some strange code that means _Large now contains several million long strings.

  Public Sub Dispose() Implements IDisposable.Dispose
    _Large=Nothing
  End Sub

I realise that the Disposable implementation does not follow current guidelines, but hopefully you all get the idea.
Now, when Dispose is called, how much memory gets freed?

Answer: None.
Calling Dispose can release unmanaged resources, it CANNOT reclaim managed memory, only the GC can do that. Thats not to say that the above isn't a good idea, following the above pattern is still a good idea in fact. Once Dispose has been run, there is nothing stopping the GC re-claiming the memory that was being used by _Large, even though the instance of LargeStuff may still be in scope. The strings in _Large may also be in gen 0 but the instance of LargeStuff might be gen 2, so again, memory would be re-claimed sooner.
There is no point in adding a finaliser to call the Dispose method shown above though. That will just DELAY the re-claiming of memory to allow the finaliser to run.


If anything, I'd expect the code to be less efficient than when leaving it out.

Calling the Clear() methods are unnecessary, and the GC probably wouldn't do that if the Dispose didn't do it...


In the example you posted, it still doesn't "free the memory now". All memory is garbage collected, but it may allow the memory to be collected in an earlier generation . You'd have to run some tests to be sure.

The Framework Design Guidelines are guidelines, and not rules. They tell you what the interface is primarily for, when to use it, how to use it, and when not to use it.

I once read code that was a simple RollBack() on failure utilizing IDisposable. The MiniTx class below would check a flag on Dispose() and if the Commit call never happened it would then call Rollback on itself. It added a layer of indirection making the calling code a lot easier to understand and maintain. The result looked something like:

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

    tx.Commit();
} 

I've also seen timing / logging code do the same thing. In this case the Dispose() method stopped the timer and logged that the block had exited.

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

So here are a couple of concrete examples that don't do any unmanaged resource cleanup, but do successfully used IDisposable to create cleaner code.


One problem with most discussions of "unmanaged resources" is that they don't really define the term, but seem to imply that it has something to do with unmanaged code. While it is true that many types of unmanaged resources do interface with unmanaged code, thinking of unmanaged resources in such terms isn't helpful.

Instead, one should recognize what all managed resources have in common: they all entail an object asking some outside 'thing' to do something on its behalf, to the detriment of some other 'things', and the other entity agreeing to do so until further notice. If the object were to be abandoned and vanish without a trace, nothing would ever tell that outside 'thing' that it no longer needed to alter its behavior on behalf of the object that no longer existed; consequently, the 'thing's usefulness would be permanently diminished.

An unmanaged resource, then, represents an agreement by some outside 'thing' to alter its behavior on behalf of an object, which would useless impair the usefulness of that outside 'thing' if the object were abandoned and ceased to exist. A managed resource is an object which is the beneficiary of such an agreement, but which has signed up to receive notification if it is abandoned, and which will use such notification to put its affairs in order before it is destroyed.


There are things that the Dispose() operation does in the example code that might have an effect that would not occur due to a normal GC of the MyCollection object.

If the objects referenced by _theList or _theDict are referred to by other objects, then that List<> or Dictionary<> object will not be subject to collection but will suddenly have no contents. If there were no Dispose() operation as in the example, those collections would still contain their contents.

Of course, if this were the situation I would call it a broken design - I'm just pointing out (pedantically, I suppose) that the Dispose() operation might not be completely redundant, depending on whether there are other uses of the List<> or Dictionary<> that are not shown in the fragment.


Yep, that code is completely redundant and unnecessary and it doesn't make the garbage collector do anything it wouldn't otherwise do (once an instance of MyCollection goes out of scope, that is.) Especially the .Clear() calls.

Answer to your edit: Sort of. If I do this:

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

It's functionally identical to this for purposes of memory management:

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

If you really really really need to free the memory this very instant, call GC.Collect() . There's no reason to do this here, though. The memory will be freed when it's needed.





idisposable