2010-12-05 8 views
9

Betrachten Sie den folgenden Code ein:Warum entsorgt ein Stream, wenn sein Brenner entsorgt wird?

using (var ms = new MemoryStream()) 
{ 
    using(var writer = BinaryWriter(ms)) 
    { 
     writer.Write(/*something*/); 
     writer.Flush(); 
    } 

    Assert.That(ms.Length > 0); // Throws ObjectDisposedException 
} 

Auf der einen Seite, ein Einweg-Objekt sollte es die Ressourcen verfügen; Ich bekomme das, aber auf der anderen Seite, das Objekt nicht erstellt und besitzt diese Ressource nicht, es wurde zur Verfügung gestellt -> Calling Code sollte die Verantwortung dafür übernehmen ... nein?

Ich kann nicht an andere Situationen wie diese denken, aber ist es ein einheitliches Muster im Rahmen für jede Klasse, die Einwegobjekte erhält, um sie auf eigene Weise zu entsorgen?

Antwort

13

Es gibt eine implizite Annahme, dass Sie nur einen Writer pro Stream haben, also übernimmt der Writer den Besitz des Streams aus Bequemlichkeit - Sie haben dann wahrscheinlich eine Sache zu bereinigen.

Aber ich stimme zu; das ist nicht immer richtig und oft unbequem. Einige Implementierungen (DeflateStream, GZipStream, zum Beispiel) erlauben Ihnen zu wählen. Andernfalls besteht die einzige echte Option darin, einen Dummy-Stream zwischen den Schreiber und den zugrunde liegenden Stream zu injizieren; IIRC gibt es eine NonClosingStreamWrapper in Jon Skeet „MiscUtil“ Bibliothek, die tut genau dies: http://www.yoda.arachsys.com/csharp/miscutil/

Usage wie etwas sein würde:

using (var ms = new MemoryStream()) 
{ 
    using(var noClose = new NonClosingStreamWrapper(ms)) 
    using(var writer = BinaryWriter(noClose)) 
    { 
        writer.Write(/*something*/); 
        writer.Flush(); 
    } 

    Assert.That(ms.Length > 0); 
} 
4

Ich stimme Ihnen vollkommen zu. Dies ist kein konsistentes Verhalten, aber es wurde so implementiert. Es gibt comments am Ende der Dokumentation über dieses Verhalten, das nicht sehr intuitiv ist. Alle Stream-Writer nehmen nur den zugrunde liegenden Stream in Besitz und entsorgen ihn. Persönlich habe ich immer Nest meine using Anweisung wie folgt:

using (var ms = new MemoryStream()) 
using(var writer = BinaryWriter(ms)) 
{ 
    writer.Write(/*something*/); 
} 

, so dass ein Code wie die, die Sie in der Assert setzen sollte nicht geschrieben werden.

0

Das Richtige getan hätte Parameter einen Konstruktor haben gewesen, Der Streamwriter zeigt an, ob der Stream bei dem Konstruktor entsorgt werden soll. Da Microsoft dies nicht getan hat, kann es sinnvoll sein, eine NonDisposingStream-Klasse (Of T as Stream) zu definieren, die einen Stream umschließt, aber keinen Dispose-Aufruf an den umschlossenen Stream weitergibt. Man könnte dann einen neuen NonDisposingStream an den Konstruktor eines StreamWriter übergeben, und der zugrunde liegende Stream wäre sicher vor der Entsorgung (es wäre natürlich notwendig, den Stream selbst zu beseitigen).

Ein Objekt, das über ein übergebenes Objekt verfügen kann, ist nützlich. Während ein solches Verhalten nicht mit dem üblichen Muster des Erschaffenden eines Objekts zusammenfällt, gibt es oft Situationen, in denen der Schöpfer eines Objekts keine Ahnung hat, wie lange das Objekt tatsächlich benötigt wird. Es kann beispielsweise erwartet werden, dass eine Methode einen neuen StreamWriter erstellt, der einen neuen Stream verwendet. Der Besitzer des StreamWriter weiß, wann er entsorgt werden sollte, kennt aber möglicherweise nicht die Existenz des inneren Streams. Der Ersteller des inneren Streams wird keine Ahnung haben, wie lange der äußere StreamWriter verwendet wird. Wenn man den StreamWriter mit dem Stream "aushändigt", wird das Entsorgungsproblem in diesem speziellen Fall gelöst.

0

Ich schlage vor, diese Wrapper-Klasse:

public class BetterStreamWriter : StreamWriter 
{ 
    private readonly bool _itShouldDisposeStream; 

    public BetterStreamWriter(string filepath) 
     :base(filepath) 
    { 
     _itShouldDisposeStream = true; 
    } 

    public BetterStreamWriter(Stream stream) 
     : base(stream) 
    { 
     _itShouldDisposeStream = false; 
    } 

    protected override void Dispose(bool disposing) 
    { 
     base.Dispose(disposing && _itShouldDisposeStream); 
    } 
} 

Objekte sollten nicht Sachen entsorgen sie instanziiert nicht. Wenn es sich um einen Datei-Stream-Writer handelt, sollte dieser entsorgt werden. Wenn es sich um einen externen Stream handelt, sollte dies nicht der Fall sein.

Sollte der Pfad zum Öffnen der Datei an erster Stelle nicht implementiert sein.Dies verletzt das Prinzip der einfachen Verantwortung, da das Objekt sowohl das Schreiben als auch die Lebensdauer der Datei verwaltet.

+1

Objekte sollten nicht über Dinge verfügen, die sie nicht besitzen *. Es ist möglich, Eigentum an anderen Objekten zu erwerben, als diese direkt zu instantiieren (am häufigsten durch Aufruf einer Factory-Methode). Da Factory-Methoden in der Regel nur ein einzelnes Objekt zurückgeben, müssen alle von der Methode erfassten Ressourcen Eigentum dieses Objekts sein. Wenn ein "StreamWriter" den Besitz eines übergebenen Streams übernimmt, ist es möglich, dass eine Factory-Methode legitim einen Stream erstellt und einen "StreamWriter" zurückgibt, der ihn einkapselt. – supercat

+1

@supercat eh nein. Ich stimme dir zu "Objekte sollten Dinge, die sie nicht besitzen, nicht entsorgen". Das ist die richtige Art, das zu sagen. Aber nicht in diesem Fall. Wenn Sie die StreamWriter-Klasse selbst entwerfen, haben Sie keine Ahnung, wie der Aufrufer mit dem Stream umgehen soll, und auch nicht. Berücksichtigen Sie Fälle, in denen ein Stream in mehreren Lesern verwendet wird. Dann müssen Sie den StreamReader bedingt mit einer using-Anweisung umbrechen, je nachdem, ob Sie ihn löschen möchten. IDisposable sollte mithilfe der Anweisung warpped werden. Keine Fragen gefragt. –

+0

Die richtige Vorgehensweise ist, dass der Konstruktor von 'StreamReader' /' StreamWriter' dem Aufrufer erlaubt anzugeben, ob der Besitz übertragen wird (in späteren Versionen von .NET). Andernfalls bedenken Sie, wie man eine Methode schreiben würde, die asynchron Daten abspielen soll, die von einem 'StreamReader' abgerufen werden. Der Code, der den Ton abspielt, kann nichts über den zugrunde liegenden Stream wissen, und der Code, der den "StreamReader" erzeugt, hat möglicherweise keine Ahnung, wann der Wiedergabecode damit gemacht wird. In dem allgemeinen Fall, in dem der Stream nur zum Zwecke der Audiowiedergabe geöffnet wurde ... – supercat

Verwandte Themen