2010-01-25 2 views
11

Schauen wir uns die berüchtigten IDisposable-Schnittstelle einen Blick:Warum IDisposable-Implementierung ist die Art und Weise entwickelt, es ist

[ComVisible(true)] 
public interface IDisposable 
{ 
    void Dispose(); 
} 

und einer typischen Implementierung, wie von MSDN empfohlen (weggelassen ich die Prüfung, ob Strom Objekt bereits angeordnet worden):

public class Base : IDisposable 
{ 
    protected virtual void Dispose(bool disposing) 
    { 
     if (disposing) 
     { 
      // release managed 
     } 
     // release unmanaged 
     disposed = true; 
    } 

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

    ~Base() 
    { 
     Dispose(false); 
    } 
} 

public class Derived : Base 
{ 
    protected override void Dispose(bool disposing) 
    { 
     base.Dispose(disposing); 
     if (disposing) 
     { 
      // release managed 
     } 
     // release unmanaged 
     disposed = true; 
    } 
} 

Problem ist: Ich denke diese Implementierung ist kontraintuitiv. Und es unterscheidet sich auch signifikant in der Basis- und abgeleiteten Klasse. Abgeleitete Klasse soll annehmen, dass diese Basisklasse IDisposable ordnungsgemäß implementiert und dann Dispose (Bool) überschreiben, die nicht einmal ein Teil der ursprünglichen Schnittstelle ist.

Ich muss zugeben, dass ich diese Frage hatte, weil ich normalerweise Junior-Programmierer anfordere, IDisposable in einem Vorstellungsgespräch zu implementieren. Wenn sie nicht genau wissen, wie es soll getan werden, kommen sie mit etwas in der Nähe auf den Punkt:

public class Base : IDisposable 
{ 
    public virtual void Dispose() 
    { 
     // release managed and unmanaged 
     GC.SuppressFinalize(this); 
    } 

    ~Base() 
    { 
     // release unmanaged 
    } 
} 

public class Derived : Base 
{ 
    public override void Dispose() 
    { 
     // release managed and unmanaged 
     base.Dispose(); 
    } 

    ~Derived() 
    { 
     // release unmanaged 
    } 
} 

Für mich ist diese Umsetzung klarer und konsistenter ist. Natürlich ist das Schlimme daran, dass wir nicht verwaltete Ressourcen an zwei verschiedenen Stellen freigeben müssen. Wichtig ist jedoch, dass wahrscheinlich über 99% der benutzerdefinierten Klassen nicht verwaltet werden, sodass sie keinen Finalizer benötigen. Ich kann einem Junior-Programmierer nicht erklären, warum MSDN-Implementierung besser ist, weil ich es selbst nicht wirklich verstehe.

Also frage ich mich, was zu so ungewöhnlichen Design-Entscheidungen führte (abgeleitete Klasse, um eine andere Methode als die in der Schnittstelle zu überschreiben und ihn über nicht verwaltete Ressourcen nachzudenken, die es höchstwahrscheinlich nicht enthält). Irgendwelche Gedanken zu diesem Thema?

Antwort

7

Also ich frage mich, was zu so ungewöhnlichen Design-Entscheidungen geführt (abgeleitete Klasse macht eine andere Methode als die in der Schnittstelle außer Kraft zu setzen und ihn über nicht verwalteten Ressourcen denken, die sie höchstwahrscheinlich doesn‘ t enthalten). Irgendwelche Gedanken zu diesem Thema?

Das Hauptproblem ist, dass IDisposable definiert wurde, NACHDEM das Framework bereits entworfen wurde und existierte. Es soll eine Situation behandeln, die verwalteter Code zu vermeiden versucht - es handelt sich also wirklich um einen Randfall, wenn es sich um einen sehr häufigen Fall handelt. ;)

Das sieht man übrigens, wenn man sich C++/CLI anschaut. Es wurde nach IDisposable entworfen und implementiert IDisposable auf eine viel natürlichere Weise (Destruktoren [~ClassName] werden automatisch zu Dispose und Finalizer [!ClassName] werden als Finalizer behandelt).

Das andere Problem ist, dass IDisposable mehrere Situationen behandelt.Ich schrieb eine entire blog series, durch die verschiedenen Implementierungen zu gehen, die verwendet werden sollten, wenn nativer Code umhüllt wird, eine Klasse kapselt, die IDisposable implementiert und sie mit faktorierten Typen verwendet.

Technisch müssen Sie nur die Schnittstelle direkt implementieren. Die Entwurfsentscheidung für eine protected virtual void Dispose(bool disposing)-Methode ermöglicht eine zusätzliche Flexibilität, die nicht einfach und sicher in einer öffentlichen Schnittstelle gehandhabt werden könnte.

0

Meines Erachtens ist der Hauptgrund für IDisposable die Freigabe nicht verwalteter Ressourcen. Daher bin ich verwirrt, warum Sie sagen: "99% benutzerdefinierte Klassen haben nichts, was nicht verwaltet werden kann" - wenn Sie IDisposable implementieren sollte sein, weil Sie nicht verwaltete Ressourcen haben.

MSDN IDisposable

+0

Falsch. Wenn Sie Ressourcen verwaltet haben, sollten Sie IDisposable weiterhin implementieren und sie verwerfen. – SLaks

+0

Nicht immer wahr. Sie kapseln möglicherweise einen Typ mit nativen Ressourcen oder implementieren einen faktorierten Typ. –

+0

@SLaks: Wenn die Ressourcen vollständig verwaltet werden, ist dies weniger wichtig (da ein vollständig verwalteter Typ vom GC korrekt gehandhabt wird) –

1

MSDN Magazine hat eine article about this pattern.

Dies beantwortet die Frage nicht ganz, aber Sie können das folgende Code-Snippet verwenden, um das Muster zu implementieren.

<?xml version="1.0" encoding="utf-8" ?> 
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> 
    <CodeSnippet Format="1.0.0"> 
     <Header> 
      <Title>Dispose pattern</Title> 
      <Shortcut>dispose</Shortcut> 
      <Description>Code snippet for virtual dispose pattern</Description> 
      <Author>SLaks</Author> 
      <SnippetTypes> 
       <SnippetType>Expansion</SnippetType> 
       <SnippetType>SurroundsWith</SnippetType> 
      </SnippetTypes> 
     </Header> 
     <Snippet> 
      <Declarations> 
       <Literal Editable="false"> 
        <ID>classname</ID> 
        <ToolTip>Class name</ToolTip> 
        <Default>ClassNamePlaceholder</Default> 
        <Function>ClassName()</Function> 
       </Literal> 
      </Declarations> 
      <Code Language="csharp"> 
       <![CDATA[ 
     ///<summary>Releases unmanaged resources and performs other cleanup operations before the $classname$ is reclaimed by garbage collection.</summary> 
     ~$classname$() { Dispose(false); } 
     ///<summary>Releases all resources used by the $classname$.</summary> 
     public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } 
     ///<summary>Releases the unmanaged resources used by the $classname$ and optionally releases the managed resources.</summary> 
     ///<param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> 
     protected virtual void Dispose(bool disposing) { 
      if (disposing) { 
       $end$$selected$ 
      } 
     }]]> 
      </Code> 
     </Snippet> 
    </CodeSnippet> 
</CodeSnippets> 
12

Normalerweise nehme ich das Rätselraten für abgeleitete Klassen heraus. Hier ist meine .snippet Datei:

#region IDisposable pattern 
/// <summary> 
/// Dispose of (clean up and deallocate) resources used by this class. 
/// </summary> 
/// <param name="fromUser"> 
/// True if called directly or indirectly from user code. 
/// False if called from the finalizer (i.e. from the class' destructor). 
/// </param> 
/// <remarks> 
/// When called from user code, it is safe to clean up both managed and unmanaged objects. 
/// When called from the finalizer, it is only safe to dispose of unmanaged objects. 
/// This method should expect to be called multiple times without causing an exception. 
/// </remarks> 
protected virtual void Dispose(bool fromUser) 
    { 
    if (fromUser) // Called from user code rather than the garbage collector 
     { 
     // Dispose of managed resources (only safe if called directly or indirectly from user code). 
     try 
      { 
     DisposeManagedResources(); 
      GC.SuppressFinalize(this); // No need for the Finalizer to do all this again. 
      } 
     catch (Exception ex) 
      { 
      //ToDo: Handle any exceptions, for example produce diagnostic trace output. 
      //Diagnostics.TraceError("Error when disposing."); 
      //Diagnostics.TraceError(ex); 
      } 
     finally 
      { 
      //ToDo: Call the base class' Dispose() method if one exists. 
      //base.Dispose(); 
      } 
     } 
    DisposeUnmanagedResources(); 
    } 
/// <summary> 
/// Called when it is time to dispose of all managed resources 
/// </summary> 
    protected virtual void DisposeManagedResources(){} 
/// <summary> 
/// Called when it is time to dispose of all unmanaged resources 
/// </summary> 
    protected virtual void DisposeUnmanagedResources(){} 
/// <summary> 
/// Dispose of all resources (both managed and unmanaged) used by this class. 
/// </summary> 
public void Dispose() 
    { 
    // Call our private Dispose method, indicating that the call originated from user code. 
    // Diagnostics.TraceInfo("Disposed by user code."); 
    this.Dispose(true); 
    } 
/// <summary> 
/// Destructor, called by the finalizer during garbage collection. 
/// There is no guarantee that this method will be called. For example, if <see cref="Dispose"/> has already 
/// been called in user code for this object, then finalization may have been suppressed. 
/// </summary> 
~$MyName$() 
    { 
    // Call our private Dispose method, indicating that the call originated from the finalizer. 
    // Diagnostics.TraceInfo("Finalizer is disposing $MyName$ instance"); 
    this.Dispose(false); 
    } 
#endregion 
+1

+1 So mache ich es auch normalerweise. – SwDevMan81

+0

Sie haben GC.SuppressFinalize nicht aufgerufen (this); –

+0

@Sergej du hast recht; Das ist nur ein Teil der ganzen Sache. Ich habe meine Antwort mit dem aktuellen Inhalt meiner .snippet-Datei aktualisiert, die eine Verschmelzung verschiedener Snippets mit persönlichen Berührungen ist. – Will

0

Ein Problem, das ich bei der Implementierung zu sehen ist, dass es potention für die abgeleitete Klasse ist nicht die Basisklasse Dispose-Methode aufrufen. In diesem Fall wird der GC.SuppressFinalize möglicherweise nicht ordnungsgemäß aufgerufen, und Sie würden auch den Finalizer aufrufen. Ich mag Wills Lösung, um sicherzustellen, dass GC.SuppressFinalize aufgerufen wird. Der empfohlene Weg von MSDN hat ein ähnliches Gefühl und stellt sicher, dass GC.SuppressFinalize aufgerufen wird, wenn das Objekt vom Entwickler entsorgt wird.

2

Die Antwort auf diese und die meisten anderen API-Designfragen finden Sie in diesem Buch.

Framework Design Guidelines: Conventions, Idiome und Muster für wiederverwendbare .NET-Bibliotheken http://www.amazon.com/gp/product/0321545613?ie=UTF8&tag=bradabramsblo-20&link_code=wql&camp=212361&creative=380601

Das ist buchstäblich der Satz von Regeln Mitarbeiter von Microsoft .NET APIs bauen verwenden. Die Regeln sind frei (siehe unten), aber das Buch enthält den Kommentar, der die Regeln erklärt. Es ist wirklich ein Muss für .NET-Entwickler.

http://msdn.microsoft.com/en-us/library/b1yfkh5e.aspx

+0

Auch in Effektiv C# Buch erwähnt –

2

Ich würde sagen, das ist better:

public class DisposableClass : IDisposable { 
    void IDisposable.Dispose() { 
    CleanUpManagedResources(); 
    CleanUpNativeResources(); 
    GC.SuppressFinalize(this); 
    } 

    protected virtual void CleanUpManagedResources() { 
    // ... 
    } 
    protected virtual void CleanUpNativeResources() { 
    // ... 
    } 

    ~DisposableClass() { 
    CleanUpNativeResources(); 
    } 
} 
0

Eine nützliche Eigenschaft der empfohlenen IDisposable Muster ist, dass es ein einheitliches Muster für abgeleitete Typen erstreckt Typen erlaubt, die IDisposable implementieren, unabhängig ob der Basistyp eine öffentliche parameterlose Methode namens Dispose verfügbar macht. Es ist wirklich kein allzu schlechtes Muster, wenn man den Parameter als Dummy ansieht, der einfach dazu verwendet wird, der geschützten Methode eine andere Signatur von einem parameterlosen Dispose() zu geben; Die größte Schwachstelle besteht darin, dass der Schutz vor redundantem Dispose nicht threadsicher durchgeführt wird.

Eine nicht so nützliche Funktion des empfohlenen IDisposable-Musters ist, dass es die Verwendung von Finalizern/Destruktoren in vielen Fällen fördert, in denen sie nicht geeignet sind. Sehr selten sollte eine Klasse, die von etwas anderem als System.Object abgeleitet ist, einen Finalizer zur Bereinigung haben (Klassen können Finalizer haben, um einen Fehler bei der ordnungsgemäßen Entsorgung zu protokollieren). Wenn eine Klasse Verweise auf viele verwaltete Objekte enthält und auch eine nicht verwaltete Ressource enthält, sollte die nicht verwaltete Ressource in eine eigene Wrapperklasse verschoben und in eine verwaltete Ressource umgewandelt werden. Diese Wrapper-Klasse kann entweder von SafeHandle abgeleitet werden, oder sie kann von Object abgeleitet werden und einen Finalizer/Destruktor definieren. Bei beiden Vorgehensweisen ist kein Finalizer/Destruktor in der Hauptklasse erforderlich.

Verwandte Themen