Gibt es eine Möglichkeit, einen StreamWriter zu schließen, ohne seinen BaseStream zu schließen?

117

Mein Grundproblem ist, dass beim usingAufruf Disposevon a StreamWriterauch das BaseStream(gleiche Problem mit Close) beseitigt wird .

Ich habe eine Problemumgehung dafür, aber wie Sie sehen können, muss der Stream kopiert werden. Gibt es eine Möglichkeit, dies zu tun, ohne den Stream zu kopieren?

Der Zweck hierbei besteht darin, den Inhalt einer Zeichenfolge (ursprünglich aus einer Datenbank gelesen) in einen Stream zu übertragen, damit der Stream von einer Komponente eines Drittanbieters gelesen werden kann.
NB : Ich kann die Komponente eines Drittanbieters nicht ändern.

public System.IO.Stream CreateStream(string value)
{
    var baseStream = new System.IO.MemoryStream();
    var baseCopy = new System.IO.MemoryStream();
    using (var writer = new System.IO.StreamWriter(baseStream, System.Text.Encoding.UTF8))
    {
        writer.Write(value);
        writer.Flush();
        baseStream.WriteTo(baseCopy); 
    }
    baseCopy.Seek(0, System.IO.SeekOrigin.Begin);
    return baseCopy;
}

Benutzt als

public void Noddy()
{
    System.IO.Stream myStream = CreateStream("The contents of this string are unimportant");
    My3rdPartyComponent.ReadFromStream(myStream);
}

Idealerweise suche ich nach einer imaginären Methode namens BreakAssociationWithBaseStreamz

public System.IO.Stream CreateStream_Alternate(string value)
{
    var baseStream = new System.IO.MemoryStream();
    using (var writer = new System.IO.StreamWriter(baseStream, System.Text.Encoding.UTF8))
    {
        writer.Write(value);
        writer.Flush();
        writer.BreakAssociationWithBaseStream();
    }
    return baseStream;
}
Binärer Worrier
quelle
Dies ist eine ähnliche Frage: stackoverflow.com/questions/2620851
Jens Granlund
Ich habe dies mit einem Stream von einem WebRequest gemacht. Interessanterweise können Sie ihn schließen, wenn die Codierung ASCII, aber nicht UTF8 ist. Seltsam.
Tofutim
tofutim, ich hatte meine als ASCII codiert, und es verfügt immer noch über den zugrunde liegenden Stream ..
Gerard ONeill

Antworten:

121

Wenn Sie .NET Framework 4.5 oder höher verwenden, gibt es eine StreamWriter-Überladung, mit der Sie den Basis-Stream auffordern können, beim Schließen des Writers offen zu bleiben .

In früheren Versionen von .NET Framework vor 4.5 wird StreamWriter davon ausgegangen, dass es den Stream besitzt. Optionen:

  • Entsorgen Sie das nicht StreamWriter; spülen Sie es einfach.
  • Erstellen Sie einen Stream-Wrapper, der Aufrufe von Close/ ignoriert, Disposeaber alles andere weiterleitet. Ich habe eine Implementierung davon in MiscUtil , wenn Sie es von dort abrufen möchten.
Jon Skeet
quelle
15
Offensichtlich war die 4.5-Überladung eine nicht durchdachte Konzession - die Überladung erfordert die Puffergröße, die weder 0 noch null sein kann. Intern weiß ich, dass 128 Zeichen die Mindestgröße sind, also setze ich sie einfach auf 1. Ansonsten macht mich diese 'Funktion' glücklich.
Gerard ONeill
Gibt es eine Möglichkeit, diesen leaveOpenParameter festzulegen, nachdem er StreamWritererstellt wurde?
c00000fd
@ c00000fd: Nicht dass ich es wüsste.
Jon Skeet
1
@Yepeekai: "Wenn ich einen Stream an eine Submethode übergebe und diese Submethode den StreamWriter erstellt, wird er am Ende der Ausführung dieser Submethode entsorgt." Nein, das stimmt einfach nicht. Es wird nur entsorgt, wenn etwas darauf ankommt Dispose. Das Methodenende macht das nicht automatisch. Es kann später finalisiert werden, wenn es einen Finalizer hat, aber das ist nicht dasselbe - und es ist immer noch nicht klar, welche Gefahr Sie erwarten. Wenn Sie der Meinung sind, dass es unsicher ist, StreamWritereine Methode zurückzugeben, weil sie vom GC automatisch entsorgt werden könnte, stimmt das einfach nicht.
Jon Skeet
1
@Yepeekai: Und IIRC StreamWriterhat keinen Finalizer - genau aus diesem Grund würde ich das nicht erwarten.
Jon Skeet
44

.NET 4.5 hat dafür eine neue Methode!

http://msdn.microsoft.com/EN-US/library/gg712853(v=VS.110,d=hv.2).aspx

public StreamWriter(
    Stream stream,
    Encoding encoding,
    int bufferSize,
    bool leaveOpen
)
maliger
quelle
Danke Kumpel! Wusste das nicht und wenn überhaupt, wäre das ein guter Grund für mich, mit dem Targeting von .NET 4.5 zu beginnen!
Vectovox
22
Schade, dass es keine Überlastung gibt, für die keine Puffergröße festgelegt werden muss. Ich bin mit der Standardeinstellung dort zufrieden. Ich muss es selbst bestehen. Nicht das Ende der Welt.
Andy McCluggage
3
Der Standardwert bufferSizeist 1024. Details finden Sie hier .
Alex Klaus
35

Rufen Sie einfach nicht Disposean StreamWriter. Der Grund, warum diese Klasse verfügbar ist, liegt nicht darin, dass sie nicht verwaltete Ressourcen enthält, sondern darin, den Stream zu entsorgen, der selbst nicht verwaltete Ressourcen enthalten könnte. Wenn die Lebensdauer des zugrunde liegenden Streams an anderer Stelle behandelt wird, muss der Writer nicht entsorgt werden.

Darin Dimitrov
quelle
2
@Marc, würde ein Aufruf Flushden Job nicht erledigen, falls er Daten puffert?
Darin Dimitrov
3
Gut, aber sobald wir CreateStream beenden, kann StreamWrtier gesammelt werden, was den Leser des dritten Teils dazu zwingt, gegen den GC anzutreten. Dies ist keine Situation, in der ich zurückbleiben möchte. Oder fehlt mir etwas?
Binary Worrier
9
@BinaryWorrier: Nein, es gibt keine Rennbedingung: StreamWriter hat keinen Finalizer (und sollte es auch nicht).
Jon Skeet
10
@Binary Worrier: Sie sollten nur dann einen Finalizer haben, wenn Sie die Ressource direkt besitzen. In diesem Fall sollte der StreamWriter davon ausgehen, dass sich der Stream bei Bedarf selbst bereinigt.
Jon Skeet
2
Es scheint, dass die 'close'-Methode von StreamWriter auch den Stream schließt und entsorgt. Man muss also den Streamwriter leeren, aber nicht schließen oder entsorgen, damit er den Stream nicht schließt, was dem Entsorgen des Streams entspricht. Viel zu viel "Hilfe" von der API hier.
Gerard ONeill
5

Der Speicherstrom verfügt über eine ToArray-Eigenschaft, die auch bei geschlossenem Strom verwendet werden kann. To Array schreibt den Stream-Inhalt unabhängig von der Position-Eigenschaft in ein Byte-Array. Sie können einen neuen Stream basierend auf dem Stream erstellen, in den Sie geschrieben haben.

public System.IO.Stream CreateStream(string value)
{
    var baseStream = new System.IO.MemoryStream();
    var baseCopy = new System.IO.MemoryStream();
    using (var writer = new System.IO.StreamWriter(baseStream, System.Text.Encoding.UTF8))
    {
        writer.Write(value);
        writer.Flush();
        baseStream.WriteTo(baseCopy); 
    }
    var returnStream = new System.IO.MemoryStream( baseCopy.ToArray());
    return returnStream;
}
Tudor
quelle
Beschränkt das das zurückgegebene Array korrekt auf die Inhaltsgröße? Denn Stream.Positionkann nicht aufgerufen werden, nachdem es entsorgt wurde.
Nyerguds
2

Sie müssen einen Nachkommen des StreamWriter erstellen und dessen Dispose-Methode überschreiben. Indem Sie immer false an den Disposing-Parameter übergeben, wird der Stream-Writer gezwungen, NICHT zu schließen. Der StreamWriter ruft nur Dispose in der Close-Methode auf, sodass dies nicht erforderlich ist überschreibe es (natürlich kannst du alle Konstruktoren hinzufügen, wenn du willst, ich habe nur einen):

public class NoCloseStreamWriter : StreamWriter
{
    public NoCloseStreamWriter(Stream stream, Encoding encoding)
        : base(stream, encoding)
    {
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(false);
    }
}
Aaron Murgatroyd
quelle
3
Ich glaube, das macht nicht das, was Sie denken. Die disposingFlagge ist ein Teil des IDisposableMusters . Wenn Sie immer falsean die Dispose(bool)Methode der Basisklasse übergeben, signalisiert dies im Wesentlichen, StreamWriterdass sie vom Finalizer aufgerufen wird (was beim Dispose()expliziten Aufruf nicht der Fall ist ) und daher nicht auf verwaltete Objekte zugreifen sollte. Aus diesem Grund wird der Basisstrom nicht entsorgt. Die Art und Weise, wie Sie dies erreicht haben, ist jedoch ein Hack. es wäre viel einfacher, einfach gar nicht erst anzurufen Dispose!
stakx - nicht mehr beitragen
Das ist wirklich Symantec. Alles, was Sie tun, außer das Streaming komplett neu zu schreiben, wird ein Hack. Auf jeden Fall könnte man base einfach nicht aufrufen. Dispose (false) überhaupt, aber es würde keinen funktionalen Unterschied geben, und ich mag die Klarheit meines Beispiels. Beachten Sie jedoch, dass eine zukünftige Version der StreamWriter-Klasse möglicherweise mehr kann, als nur den Stream zu schließen, wenn er verfügbar ist. Wenn Sie also dispose (false) future aufrufen, wird dies auch bewiesen. Aber jedem sein eigenes.
Aaron Murgatroyd
2
Eine andere Möglichkeit wäre, einen eigenen Stream-Wrapper zu erstellen, der einen anderen Stream enthält, bei dem die Close-Methode einfach nichts tut, anstatt den zugrunde liegenden Stream zu schließen. Dies ist weniger ein Hack, sondern mehr Arbeit.
Aaron Murgatroyd
Erstaunliches Timing: Ich wollte gerade das Gleiche vorschlagen (Dekorateurklasse, möglicherweise benannt OwnedStream, die ignoriert Dispose(bool)und Close).
stakx - nicht mehr beitragen
Ja, der obige Code ist, wie ich es für eine schnelle und schmutzige Methode mache, aber wenn ich eine kommerzielle Anwendung oder etwas machen würde, das mir wirklich wichtig ist, würde ich es korrekt mit der Stream-Wrapper-Klasse machen. Persönlich denke ich, dass Microsoft hier einen Fehler gemacht hat, der Streamwriter hätte eine boolesche Eigenschaft haben sollen, um stattdessen den zugrunde liegenden Stream zu schließen, aber dann arbeite ich nicht bei Microsoft, damit sie tun, was sie wollen, denke ich: D
Aaron Murgatroyd