2009-06-19 12 views
18

Qualche giorno fa ho postato qualche codice come questo:È necessario avvolgere StreamWriter in un blocco usando?

StreamWriter writer = new StreamWriter(Response.OutputStream); 
writer.WriteLine("col1,col2,col3"); 
writer.WriteLine("1,2,3"); 
writer.Close(); 
Response.End(); 

mi è stato detto che invece avrei dovuto avvolgere StreamWriter in un blocco utilizzando in caso di eccezioni. Un tale cambiamento lo farebbe apparire così:

using(StreamWriter writer = new StreamWriter(Response.OutputStream)) 
{ 
    writer.WriteLine("col1,col2,col3"); 
    writer.WriteLine("1,2,3"); 
    writer.Close(); //not necessary I think... end of using block should close writer 
} 
Response.End(); 

Non so perché questo è un cambiamento prezioso. Se si è verificata un'eccezione senza il blocco using, lo scrittore e la risposta verrebbero comunque ripuliti, giusto? Cosa mi guadagna il blocco usando?

risposta

21

No, il flusso rimarrebbe aperto nel primo esempio, poiché l'errore annullerebbe la chiusura di esso.

L'operatore che utilizza forza la chiamata di Dispose() che deve pulire l'oggetto e chiudere tutte le connessioni aperte quando esce dal blocco.

3

Se si verifica un'eccezione senza utilizzare il blocco e si interrompe il programma, si rimarrà le connessioni opzionali. Il blocco using chiuderà sempre la connessione per te, come se tu dovessi usare try {} catch {} finally {}

+1

Se il programma si chiude, le maniglie avranno ripulito. L'istruzione using aiuta solo mentre l'app è in esecuzione. –

+0

Finalizzatori sono il termine per ciò che fa la pulizia all'uscita. – Guvante

1

A mio parere è necessario avvolgere qualsiasi classe che implementa IDisposable in un blocco using. Il fatto che una classe implementa IDisposable significa che la classe ha risorse che devono essere ripulite.

+1

Cosa fare se è necessario mantenere lo stato aperto per un periodo di tempo più lungo. Come prese e flussi asincroni. C'è un motivo per cui puoi impiantare IDisposible da solo. Anche IDisposable chiuderà anche il flusso. Ciò significa che se si reindirizza contenuti come da uno streamwriter come sopra e si dispone dello streamwriter, lo streamwriter chiuderà il flusso trasmesso. questo potrebbe impedire l'invio dei dati al client. –

0

il blocco di utilizzo chiama dispose() quando finisce. È solo un modo pratico per garantire che le risorse vengano pulite in modo tempestivo.

+0

Idea sbagliata comune, Dispose è un modo per dire ai consumatori della tua classe che l'oggetto deve essere ripulito, Finalizzatori è il modo in cui il garbage collector pulisce quando l'oggetto lascia l'ambito o il programma finisce. – Guvante

+0

http://msdn.microsoft.com/en-us/library/system.idisposable.dispose(VS.71).aspx "Utilizzare questo metodo per chiudere o rilasciare risorse non gestite come file, flussi e maniglie detenute da un istanza della classe che implementa questa interfaccia.Questo metodo è, per convenzione, utilizzato per tutte le attività associate alla liberazione delle risorse detenute da un oggetto, o alla preparazione di un oggetto da riutilizzare. " – Robert

0

In quasi tutti i casi, se una classe implementa IDisposable, e se si sta creando un'istanza di quella classe, allora è necessario il blocco using.

+0

@John - ci sono delle eccezioni? –

+0

Solo proxy client WCF, per quanto ne so. Ciò è dovuto a un errore di progettazione da parte di Microsoft. –

+0

@ John: come pensi che si tratti di un errore? Ci sono volte in cui devi lasciare aperto il vapore in uscita e chiuderlo solo quando il client è completo. Se lo si chiude prima che il client abbia scaricato tutti i dati, la connessione sarebbe stata interrotta e il client non avrebbe mai ricevuto i dati –

3

Alla fine, lo scrittore verrà ripulito. Quando ciò accade dipende dal garbage collector, che noterà che il comando Dispose per il comando non è stato chiamato e lo invoca. Ovviamente, il GC potrebbe non funzionare per minuti, ore o giorni a seconda della situazione. Se lo scrittore è in possesso di un blocco esclusivo per dire, un file, nessun altro processo sarà in grado di aprirlo, anche se hai una lunga durata.

Il blocco di utilizzo garantisce che venga sempre effettuata la chiamata di Dispose e quindi che la chiusura venga sempre chiamata, indipendentemente dal flusso di controllo.

+0

Come si pulisce? Chiama il proprio metodo Dispose in un finalizzatore? – xyz

+0

In genere per i riferimenti dei membri alle risorse non gestite, lo stesso codice di cleanup verrà chiamato da entrambi. Ho detto "Richiamalo" sopra, ma non c'è certezza che venga chiamata la funzione Dispose. Il comportamento sarà probabilmente molto simile in entrambi i casi però. –

+0

Il GC * non * chiamerà mai il metodo Dispose di un oggetto. Se l'oggetto ha un finalizzatore, alla fine dovrebbe essere chiamato dal GC, ed è possibile che il finalizzatore possa quindi chiamare manualmente Dispose (indipendentemente dal fatto che sia o meno un dettaglio di implementazione). – LukeH

3

Avvolgere la StreamWriter in un blocco using è praticamente equivalente del seguente codice:

StreamWriter writer; 
try 
{ 
    writer = new StreamWriter(Response.OutputStream); 
    writer.WriteLine("col1,col2,col3"); 
    writer.WriteLine("1,2,3"); 
} 
catch 
{ 
    throw; 
} 
finally 
{ 
    if (writer != null) 
    { 
     writer.Close();  
    } 
} 

Mentre si potrebbe benissimo scrivere questo codice da soli, è molto più facile appena messo in un blocco utilizzando .

+1

chiudendo StreamWriter cloderà lo scopo .OutputStream pure. Solo un avvertimento se trovi che i tuoi dati non arrivano al cliente. –

+1

Basta omettere la parte catch() {}, non serve a nulla e può solo essere confusa. –

1

La mia regola generale è, se vedo Dispose elencato in intellisense, lo avvolgo in un blocco using.

+0

Buona regola, ma non abbastanza. Alcune classi utilizzano un'implementazione esplicita di IDisposable, nascondendo in modo efficace Dispose(). Cattiva idea, ma è in uso. Hai ancora bisogno di applicare utilizzando() {} –

0

Mentre è buona norma dipovare sempre classi usa e getta come StreamWriter, come altri sottolineano, in questo caso non importa.

Response.OutputStream verrà smaltito dall'infrastruttura ASP.NET al termine dell'elaborazione della richiesta.

StreamWriter presuppone che "sia proprietario" di un flusso trasmesso al costruttore e pertanto chiude il flusso quando è eliminato.Ma nell'esempio fornito, lo stream è stato istanziato all'esterno del codice, quindi ci sarà un altro proprietario responsabile della pulizia.

+0

Che dire di StreamWriter? Cosa succede se si tiene su alcune risorse non gestite a parte il flusso che avvolge? –

+0

Non ha altre risorse non gestite. Ma sono d'accordo che è una buona pratica disporne, sto solo sottolineando che in questa situazione, il flusso sarà disposto in entrambi i modi. – Joe

+0

Se non si dispone di StreamWriter, è necessario chiamare Flush() una volta terminato. Altrimenti parte dell'output potrebbe essere ancora memorizzata in memoria e non ancora scritta nello stream. –

16

Ho intenzione di dare l'opinione dissenziente. La risposta alla domanda specifica "È necessario avvolgere StreamWriter in un blocco usando?" è in realtà No. In effetti, si non deve chiamare Dispose su StreamWriter, perché Dispose è progettato male e fa la cosa sbagliata.

Il problema con StreamWriter è che, quando lo si elimina, disattiva il flusso sottostante. Se hai creato StreamWriter con un nome file e ha creato internamente il proprio FileStream, questo comportamento sarebbe del tutto appropriato. Ma se, come qui, hai creato StreamWriter con uno stream esistente, allora questo comportamento è assolutamente The Wrong Thing (tm). Ma lo fa comunque.

codice come questo non funzionerà:

var stream = new MemoryStream(); 
using (var writer = new StreamWriter(stream)) { ... } 
stream.Position = 0; 
using (var reader = new StreamReader(stream)) { ... } 

perché quando using blocco del StreamWriter dispone la StreamWriter, che a sua volta buttare via la corrente. Quindi quando provi a leggere dallo stream, ottieni una ObjectDisposedException.

StreamWriter è un'orribile violazione della regola "ripulisci il tuo casino". Cerca di ripulire il caos di qualcun altro, che lo volessero o meno.

(immaginate se si è tentato questo nella vita reale. Prova spiegare alla polizia perché si è rotto nella casa di qualcun altro e cominciò a gettare tutte le loro cose nel cestino ...)

Per questo motivo, ho considera StreamWriter (e StreamReader, che fa la stessa cosa) tra le poche classi in cui "se implementa IDisposable, dovresti chiamare Dispose" è sbagliato. Mai chiamata Dispose su StreamWriter che è stato creato su uno stream esistente. Chiama Flush() invece.

Quindi assicurati di ripulire il flusso quando necessario. (Come Joe ha sottolineato, ASP.NET dispone la Response.OutputStream per voi, quindi non c'è bisogno di preoccuparsi qui.)

Attenzione: se non gettare lo StreamWriter, allora si fai devi chiamare Flush() quando hai finito di scrivere. In caso contrario, è possibile che i dati vengano ancora memorizzati in memoria in memoria e che non giungano mai al flusso di output.

La mia regola per StreamReader è, fingere di non implementare IDisposable. Lascia perdere quando hai finito.

La mia regola per StreamWriter è, chiama Flush dove altrimenti avresti chiamato Dispose. (Questo significa che dovete usare un try .. finally invece di un using.)

+0

"La mia regola per StreamReader è, fingere di non implementare IDisposable" - ma non se hai costruito lo StreamReader usando un nome file, poiché in questo caso possiede lo stream. Non sono d'accordo sul fatto che StreamReader/Writer sia progettato male. Mentre capisco i problemi che descrivi, tutto il design è bilanciato e penso che il comportamento sia giusto per la maggior parte dei casi d'uso. Certo nel tuo esempio sopra, ora devi annidare le tue affermazioni usando, ma non è un grosso problema. C'è qualche discussione sulla responsabilità di smaltire questa domanda: http://stackoverflow.com/questions/674879 – Joe

+0

@Joe: la tua anti-regola è troppo forte. Dovrebbe essere, non chiamare Dispose (quindi non implementare un blocco using) su StreamReader/StreamWriter a meno che non si intenda chiudere il flusso sottostante. Forse ciò che è necessario è qualcosa come il flag CloseInput su XmlReaderSettings, ma per StreamReader. –

+0

@Joe: vero. Quello che dovevano * aver * fatto era, se lo si costruisce con un nome file, quindi il costruttore crea uno stream e Dispose lo chiude; ma se lo costruisci con uno stream, non lo chiude, perché lo possiede qualcun altro. –

Problemi correlati