C'è un modo per chiudere uno StreamWriter senza chiudere il suo BaseStream?


117

Il mio problema principale è che quando usingchiama Disposea StreamWriter, elimina anche lo BaseStream(stesso problema con Close).

Ho una soluzione alternativa per questo, ma come puoi vedere, si tratta di copiare lo stream. C'è un modo per farlo senza copiare lo stream?

Lo scopo di ciò è ottenere il contenuto di una stringa (originariamente letta da un database) in uno stream, in modo che lo stream possa essere letto da un componente di terze parti.
NB : non posso cambiare il componente di terze parti.

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;
}

Usato come

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

Idealmente sto cercando un metodo immaginario chiamato BreakAssociationWithBaseStream, ad es

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;
}

Questa è una domanda simile: stackoverflow.com/questions/2620851
Jens Granlund

Lo stavo facendo con un flusso da un WebRequest, interessante, puoi chiuderlo se la codifica è ASCII ma non UTF8. Strano.
fino al

tofutim, avevo il mio codificato come ASCII, e dispone ancora del flusso sottostante ..
Gerard ONeill

Risposte:


121

Se si utilizza .NET Framework 4.5 o versioni successive, è presente un overload StreamWriter che consente di chiedere al flusso di base di essere lasciato aperto quando il writer viene chiuso .

Nelle versioni precedenti di .NET Framework precedenti alla 4.5, si StreamWriter presume che sia il proprietario del flusso. Opzioni:

  • Non smaltire il StreamWriter; basta sciacquarlo.
  • Crea un wrapper di flusso che ignora le chiamate a Close/ Disposema invia tramite proxy tutto il resto. Ho un'implementazione di questo in MiscUtil , se vuoi prenderlo da lì.

15
Chiaramente il sovraccarico 4,5 era una concessione non pensata: il sovraccarico richiede la dimensione del buffer, che non può essere 0 né nulla. Internamente so che 128 caratteri è la dimensione minima, quindi l'ho impostata su 1. Altrimenti questa "caratteristica" mi rende felice.
Gerard ONeill

C'è un modo per impostare quel leaveOpenparametro dopo che è StreamWriterstato creato?
c00000fd

@ c00000fd: Non che io sappia.
Jon Skeet

1
@Yepeekai: "se passo un flusso a un sotto-metodo e quel sotto-metodo crea lo StreamWriter, verrà eliminato alla fine dell'esecuzione di quel sotto-metodo" No, semplicemente non è vero. Sarà smaltito solo se qualcosa lo richiede Dispose. La fine del metodo non lo fa automaticamente. Potrebbe essere finalizzato in un secondo momento se ha un finalizzatore, ma non è la stessa cosa e non è ancora chiaro quale pericolo stai anticipando. Se ritieni che non sia sicuro restituire un StreamWriterda un metodo perché potrebbe essere eliminato automaticamente dal GC, non è vero.
Jon Skeet,

1
@Yepeekai: E IIRC, StreamWriternon ha un finalizzatore - non me lo aspetto, proprio per questo motivo.
Jon Skeet,

44

.NET 4.5 ha un nuovo metodo per questo!

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
)

Grazie compagno! Non lo sapevo, e semmai sarebbe un buon motivo per iniziare a prendere di mira .NET 4.5!
Vectovox

22
Peccato che non ci sia un sovraccarico che non richieda l'impostazione di bufferSize. Sono soddisfatto dell'impostazione predefinita lì. Devo passarlo io stesso. Non è la fine del mondo.
Andy McClubags

3
L'impostazione predefinita bufferSizeè 1024. I dettagli sono qui .
Alex Klaus

35

Semplicemente non chiamare Disposeil StreamWriter. Il motivo per cui questa classe è usa e getta non è perché contiene risorse non gestite ma per consentire l'eliminazione del flusso che a sua volta potrebbe contenere risorse non gestite. Se la vita del flusso sottostante viene gestita altrove, non è necessario eliminare il writer.


2
@ Marc, la chiamata non Flushfarebbe il lavoro nel caso in cui memorizzi i dati?
Darin Dimitrov

3
Va bene, ma una volta usciti da CreateStream, lo StreamWrtier è collezionabile, costringendo il lettore della terza parte a gareggiare contro il GC, che non è una situazione in cui voglio essere lasciato. O mi manca qualcosa?
Binary Worrier

9
@BinaryWorrier: No, non ci sono condizioni di gara: StreamWriter non ha un finalizzatore (e in effetti non dovrebbe).
Jon Skeet

10
@Binary Worrier: dovresti avere un finalizzatore solo se possiedi direttamente la risorsa. In questo caso, StreamWriter dovrebbe presumere che lo Stream si pulirà da solo se necessario.
Jon Skeet

2
Sembra che anche il metodo "close" di StreamWriter chiuda ed elimini lo stream. Quindi è necessario scaricare, ma non chiudere o eliminare lo streamwriter, in modo che non lo chiuda, il che equivale a smaltire lo stream. Troppo "aiuto" dall'API qui.
Gerard ONeill

5

Il flusso di memoria ha una proprietà ToArray che può essere utilizzata anche quando il flusso è chiuso. To Array scrive il contenuto del flusso in una matrice di byte, indipendentemente dalla proprietà Position. Puoi creare un nuovo stream in base allo stream in cui hai scritto.

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;
}

Ciò limita correttamente l'array restituito alla dimensione del contenuto? Perché nonStream.Position può essere chiamato dopo che è stato smaltito.
Nyerguds

2

È necessario creare un discendente dello StreamWriter e sovrascrivere il suo metodo dispose, passando sempre false al parametro disposing, forzerà la chiusura dello stream writer NON, lo StreamWriter chiama solo dispose nel metodo close, quindi non è necessario sovrascriverlo (ovviamente puoi aggiungere tutti i costruttori se vuoi, ne ho solo uno):

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

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

3
Credo che questo non faccia quello che pensi che faccia. La disposingbandiera è parte del IDisposablemodello . Passare sempre falseal Dispose(bool)metodo della classe base segnala fondamentalmente al StreamWriterche viene chiamato dal finalizzatore (cosa che non è così quando si chiama Dispose()esplicitamente), e quindi non dovrebbe accedere ad alcun oggetto gestito. Questo è il motivo per cui non smaltirà il flusso di base. Tuttavia, il modo in cui hai ottenuto questo risultato è un trucco; sarebbe molto più semplice semplicemente non chiamare Disposein primo luogo!
stakx - non contribuisce più l'

Quello è davvero di Symantec, qualsiasi cosa tu faccia oltre a riscrivere lo streaming interamente da zero sarà un hack. Sicuramente, però, potresti semplicemente non chiamare base.Dispose (falso), ma non ci sarebbe alcuna differenza funzionale, e mi piace la chiarezza del mio esempio. Tuttavia, tienilo a mente, una versione futura della classe StreamWriter potrebbe fare di più che chiudere il flusso quando viene smaltito, quindi anche chiamare dispose (false) lo dimostra. Ma a ciascuno il suo.
Aaron Murgatroyd

2
Un altro modo per farlo sarebbe creare il proprio stream wrapper che contiene un altro flusso in cui il metodo Close semplicemente non fa nulla invece di chiudere il flusso sottostante, questo è meno di un trucco ma è un lavoro più.
Aaron Murgatroyd

Tempismo incredibile: stavo per suggerire la stessa cosa (classe di decoratore, forse denominata OwnedStream, che ignora Dispose(bool)e Close).
stakx - non contribuisce più il

Sì, il codice sopra è il modo in cui lo faccio per un metodo veloce e sporco, ma se stessi realizzando un'applicazione commerciale o qualcosa che mi interessa davvero, lo farei correttamente usando la classe wrapper Stream. Personalmente penso che Microsoft abbia commesso un errore qui, lo streamwriter avrebbe dovuto avere una proprietà booleana per chiudere invece il flusso sottostante, ma poi non lavoro in Microsoft quindi fanno quello che vogliono immagino: D
Aaron Murgatroyd
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.