2009-09-13 13 views
20

Ich glaube nicht, dass diese Frage zuvor gestellt wurde. Ich bin ein wenig verwirrt über die beste Möglichkeit, IDisposable auf einer versiegelten Klasse zu implementieren - speziell eine versiegelte Klasse, die nicht von einer Basisklasse erbt. (Das heißt, eine "reine versiegelte Klasse", die meine Bezeichnung ist.)Implementieren IDisposable auf einer versiegelten Klasse

Vielleicht stimmen einige von Ihnen darin überein, dass die Richtlinien für die Implementierung IDisposable sehr verwirrend sind. Das heißt, ich möchte wissen, dass die Art, wie ich vorhabe, IDisposable zu implementieren, ausreichend und sicher ist.

Ich mache einige P/Invoke-Code, der eine IntPtr durch Marshal.AllocHGlobal zuweist und natürlich möchte ich den nicht verwalteten Speicher, den ich erstellt habe sauber zu entsorgen. So denke an Ich bin so etwas wie dies

using System.Runtime.InteropServices; 

[StructLayout(LayoutKind.Sequential)] 
public sealed class MemBlock : IDisposable 
{ 
    IntPtr ptr; 
    int length; 

    MemBlock(int size) 
    { 
      ptr = Marshal.AllocHGlobal(size); 
      length = size; 
    } 

    public void Dispose() 
    { 
      if (ptr != IntPtr.Zero) 
      { 
       Marshal.FreeHGlobal(ptr); 
       ptr = IntPtr.Zero; 
       GC.SuppressFinalize(this); 
      } 
    } 

    ~MemBlock() 
    { 
      Dispose(); 
    }  
} 

Ich gehe davon aus, dass, weil MemBlock vollständig abgedichtet ist und nie von einer anderen Klasse abgeleitet wird, dass ein virtual protected Dispose(bool disposing) Implementierung ist nicht erforderlich.

Ist der Finalizer auch unbedingt erforderlich? Alle Gedanken willkommen.

Antwort

13

Der Finalizer ist als Fallback-Mechanismus erforderlich, um eventuell nicht verwaltete Ressourcen freizugeben, wenn Sie vergessen haben, Dispose anzurufen.

Nein, Sie sollten keine virtual Methode in einer sealed Klasse deklarieren. Es würde überhaupt nicht kompilieren. Es wird auch nicht empfohlen, neue protected Mitglieder in sealed Klassen zu deklarieren.

+0

Aber natürlich wäre in dem Fall, dass eine versiegelte Klasse von einer Basisklasse abgeleitet wäre, eine virtuelle Dispose notwendig - richtig? – zebrabox

+0

Auch. Der Finalisierer bedeutet, dass er zu einer Finalizer-Warteschlange hinzugefügt wird und den Overhead einer effektiven doppelten Müllsammlung aufweist. Es scheint eine schwere Strafe für die Verwendung von nicht verwalteten Ressourcen zu zahlen. Gibt es keine Möglichkeit, den Leistungseinbruch zu vermeiden? – zebrabox

+0

In diesem Fall würden Sie die Methode überschreiben. Sie können keine Methoden in einer "versiegelten" Klasse als "virtuell" deklarieren. Es ist ein Compiler ** Fehler **. –

7

Ein kleiner Zusatz; in der allgemeine Fall, ein gemeinsames Muster ist eine Dispose(bool disposing) Methode, so dass Sie wissen, ob Sie in Dispose sind (wo mehr Dinge verfügbar sind) vs Finalizer (wo Sie wirklich keine anderen verbundenen verwalteten Objekte berühren sollten) .

Zum Beispiel:

public void Dispose() { Dispose(true); } 
~MemBlock() { Dispose(false); } 
void Dispose(bool disposing) { // would be protected virtual if not sealed 
    if(disposing) { // only run this logic when Dispose is called 
     GC.SuppressFinalize(this); 
     // and anything else that touches managed objects 
    } 
    if (ptr != IntPtr.Zero) { 
      Marshal.FreeHGlobal(ptr); 
      ptr = IntPtr.Zero; 
    } 
} 
+0

Ja, guter Punkt, Marc, aber wenn ich wüsste, dass ich nur unmanaged Ressourcen entsorge, dann ist das unbedingt notwendig? – zebrabox

+0

Auch - sehr blöde Frage, aber wenn das Dispose-Muster deterministisch nicht verwaltete Ressourcen freigibt, warum sollte ich dann mit verwaisten Ressourcen umgehen, wenn sie vom GC bereinigt werden sollten? – zebrabox

+0

Wenn Sie deterministisch sind, dann würden Sie alles aufräumen wollen, was Sie * einkapseln *; besonders wenn sie selbst "IDisposable" sind. Sie * würden das nicht im Finalizer tun, da sie vielleicht bereits gesammelt wurden (und: es ist nicht mehr Ihre Aufgabe). Und du hast recht; In diesem Fall, außer dem 'SuppressFinalize' (was nicht so wichtig ist), machen wir nichts, was verwaltet wird, also wäre es in Ordnung, sich nicht darum zu kümmern; deshalb habe ich den * allgemeinen * Fall hervorgehoben. –

7

Von Joe Duffy's Weblog:

Für versiegelte Klassen, dieses Muster müssen nicht gefolgt werden, das heißt, Sie sollten einfach Ihre Finalizer implementieren und Entsorgen mit den einfachen Methoden (zB ~ T() (Finalisieren) und Dispose() in C#). Wenn Sie die letztere Route wählen, sollte Ihr Code immer noch den folgenden Richtlinien in Bezug auf Implementierung der Finalisierung und Dispose Logik entsprechen.

Also ja, du solltest gut sein.

Sie brauchen den Finalizer wie Mehrdad erwähnt. Wenn Sie es vermeiden möchten, können Sie sich SafeHandle ansehen. Ich habe nicht genug Erfahrung mit P/Invoke, um die korrekte Verwendung vorzuschlagen.

+0

Danke TrueWill! Ich habe SafeHandle betrachtet und laut Eric Lippert wurde es vom BCL-Team als einer der wichtigsten Vorteile angesehen, die sie in "Whidbey" eingeführt haben (den Link kann ich leider nicht finden). Leider ist es eine abstrakte Klasse, also müssen Sie für jede Situation rollen, die irgendwie saugt. – zebrabox

+1

@zebrabox: Während Sie vielleicht in einigen Situationen Ihre eigenen rollen müssen, heißt es in der Dokumentation: "Eine Reihe von vorgefertigten Klassen abgeleitet von SafeHandle wird als bereitgestellt abstrakte Ableitungen, und diese Menge befindet sich im Microsoft.Win32.SafeHandles-Namespace. " – TrueWill

+1

@TrueWill. Yep sehr wahr, aber nur für Sachen wie Dateigriffe, Wartegriffe, Pipe-Griffe und eine Menge Crypt-Zeug. Immer noch besser als nichts! – zebrabox

1

Sie können virtuelle Methoden in einer versiegelten Klasse nicht deklarieren. Wenn Sie auch geschützte Member in einer versiegelten Klasse deklarieren, erhalten Sie eine Compilerwarnung. Du hast es also richtig implementiert. Der Aufruf von GC.SuppressFinalize (this) aus dem Finalizer ist aus offensichtlichen Gründen nicht erforderlich, kann aber nicht schaden.

Ein Finalizer ist wichtig, wenn es um nicht verwaltete Ressourcen geht, da sie nicht automatisch freigegeben werden. Sie müssen dies im Finalizer tun, wenn das Objekt automatisch aufgerufen wird, nachdem das Objekt gesammelt wurde.

Verwandte Themen