Devo chiamare Close () o Dispose () per gli oggetti stream?


151

Classi come Stream, StreamReader, StreamWriterecc implementa IDisposablel'interfaccia. Ciò significa che possiamo chiamare il Dispose()metodo su oggetti di queste classi. Hanno anche definito un publicmetodo chiamato Close(). Ora questo mi confonde, su cosa dovrei chiamare una volta che ho finito con gli oggetti? Cosa succede se chiamo entrambi?

Il mio codice attuale è questo:

using (Stream responseStream = response.GetResponseStream())
{
   using (StreamReader reader = new StreamReader(responseStream))
   {
      using (StreamWriter writer = new StreamWriter(filename))
      {
         int chunkSize = 1024;
         while (!reader.EndOfStream)
         {
            char[] buffer = new char[chunkSize];
            int count = reader.Read(buffer, 0, chunkSize);
            if (count != 0)
            {
               writer.Write(buffer, 0, count);
            }
         }
         writer.Close();
      }
      reader.Close();
   }
}

Come vedi, ho scritto using()costrutti, che chiamano automaticamente Dispose()metodo su ogni oggetto. Ma chiamo anche Close()metodi. È giusto?

Per favore, suggeriscimi le migliori pratiche quando usi gli oggetti stream. :-)

L'esempio MSDN non usa using()costrutti e chiama il Close()metodo:

È buono?


Se stai usando ReSharper, potresti definirlo "antipattern" nel catalogo di patter. ReSharper contrassegnerà ogni utilizzo come errore / suggerimento / avviso relativo alla propria definizione. È anche possibile definire come ReSharper deve applicare un QuickFix per tale evenienza.
Thorsten Hans,

3
Solo un consiglio: puoi usare l'istruzione using come quella per più iten usa e getta: usando (Stream responseStream = response.GetResponseStream ()) usando (StreamReader reader = new StreamReader (responseStream)) usando (StreamWriter writer = new StreamWriter (nome file)) {//..Alcuni codici}
Latrova,


Non è necessario nidificare le istruzioni using in modo tale da poterle sovrapporre e avere un set di parentesi. In un altro post, ho suggerito una modifica per uno snippet di codice che avrebbe dovuto utilizzare istruzioni con quella tecnica se si desidera cercare e correggere la "freccia del codice": stackoverflow.com/questions/5282999/…
Timothy Gonzalez

2
@ Suncat2000 Puoi avere più istruzioni using, ma non annidarle e invece impilarle. Non voglio dire la sintassi come questo che limita il tipo: using (MemoryStream ms1 = new MemoryStream(), ms2 = new MemoryStream()) { }. Voglio dire così dove puoi ridefinire il tipo:using (MemoryStream ms = new MemoryStream()) using (FileStream fs = File.OpenRead("c:\\file.txt")) { }
Timothy Gonzalez,

Risposte:


101

Un rapido salto in Reflector.NET mostra che il Close()metodo StreamWriterè:

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

Ed StreamReaderè:

public override void Close()
{
    this.Dispose(true);
}

L' Dispose(bool disposing)override in StreamReaderè:

protected override void Dispose(bool disposing)
{
    try
    {
        if ((this.Closable && disposing) && (this.stream != null))
        {
            this.stream.Close();
        }
    }
    finally
    {
        if (this.Closable && (this.stream != null))
        {
            this.stream = null;
            /* deleted for brevity */
            base.Dispose(disposing);
        }
    }
}

Il StreamWritermetodo è simile

Quindi, leggendo il codice, è chiaro che puoi chiamare Close()e Dispose()in streaming tutte le volte che vuoi e in qualsiasi ordine. Non cambierà il comportamento in alcun modo.

Quindi dipende dal fatto che sia più leggibile o meno da usare Dispose(), Close()e / o using ( ... ) { ... }.

La mia preferenza personale è che using ( ... ) { ... }dovrebbe sempre essere usato quando possibile in quanto ti aiuta a "non correre con le forbici".

Ma mentre ciò aiuta la correttezza, riduce la leggibilità. In C # abbiamo già una pletora di parentesi graffe chiuse, quindi come facciamo a sapere quale esegue effettivamente la chiusura sul flusso?

Quindi penso che sia meglio fare questo:

using (var stream = ...)
{
    /* code */

    stream.Close();
}

Non influisce sul comportamento del codice, ma aiuta la leggibilità.


20
" In C # abbiamo già una pletora di parentesi graffe chiuse, quindi come facciamo a sapere quale esegue effettivamente la chiusura sul flusso? " Non penso che questo sia un grosso problema: il flusso è chiuso "al momento giusto", cioè, quando la variabile esce dal campo di applicazione e non è più necessaria.
Heinzi,

110
Hmm, no, questo è un "perché diamine sta chiudendolo due volte ??" dosso durante la lettura.
Hans Passant,

57
Non sono d'accordo con la Close()chiamata ridondante . Se qualcuno meno esperto guarda il codice e non lo sa using: 1) cerca e impara , o 2) aggiungi un cieco Close()manualmente. Se sceglie 2), forse un altro sviluppatore vedrà il ridondante Close()e invece di "ridacchiare", istruire lo sviluppatore meno esperto. Non sono favorevole a rendere la vita difficile per sviluppatori inesperti, ma sono favorevole a trasformarli in sviluppatori esperti.
R. Martinho Fernandes,

14
Se si utilizza + Chiudi () e si attiva / analizza, viene visualizzato "avviso: CA2202: Microsoft.Usage: l'oggetto 'f' può essere eliminato più di una volta nel metodo 'Foo (stringa)'. Per evitare di generare un sistema. ObjectDisposedException non dovresti chiamare Dispose più di una volta su un oggetto .: Lines: 41 "Quindi, mentre l'implementazione corrente va bene con la chiamata Close and Dispose, secondo la documentazione e / analisi, non va bene e potrebbe cambiare nelle versioni future di. netto.
marc40000,

4
+1 per la buona risposta. Un'altra cosa da considerare. Perché non aggiungere un commento dopo la parentesi graffa di chiusura come // Close o mentre lo faccio, essendo un novizio, aggiungo una riga dopo ogni parentesi graffa di chiusura che non è chiara. come ad esempio in una lunga classe aggiungerei // End Namespace XXX dopo la parentesi graffa di chiusura finale e // End Class YYY dopo la seconda parentesi di chiusura finale. Non è questo ciò a cui servono i commenti. Solo curioso. :) Come un novizio, ho visto un tale codice, quindi il motivo per cui sono venuto qui. Ho fatto la domanda "Perché la necessità della seconda chiusura". Sento linee extra di codice che non aggiungono chiarezza. Scusate.
Francis Rodgers,

51

No, non dovresti chiamare questi metodi manualmente. Alla fine del usingblocco Dispose()viene automaticamente chiamato il metodo che si occuperà di liberare risorse non gestite (almeno per le classi .NET BCL standard come stream, lettori / scrittori, ...). Quindi potresti anche scrivere il tuo codice in questo modo:

using (Stream responseStream = response.GetResponseStream())
    using (StreamReader reader = new StreamReader(responseStream))
        using (StreamWriter writer = new StreamWriter(filename))
        {
            int chunkSize = 1024;
            while (!reader.EndOfStream)
            {
                 char[] buffer = new char[chunkSize];
                 int count = reader.Read(buffer, 0, chunkSize);
                 if (count != 0)
                 {
                     writer.Write(buffer, 0, count);
                 }
            }
         }

Il Close()metodo chiama Dispose().


1
Sono abbastanza sicuro che non devi essere usingil primo responseStreampoiché è avvolto dal readerche si assicurerà che si chiuda quando il lettore è disposto. +1 tuttavia
Isak Savo,

Questo è confuso quando hai detto The Close method calls Dispose.... e nel resto del tuo post, stai insinuando che Dispose()chiamerei Close(), non dovrei chiamare quest'ultimo manualmente. Stai dicendo che si chiamano?
Nawaz,

@Nawaz, il mio post era confuso. Il metodo Close chiama semplicemente Dispose. Nel tuo caso devi disporre di Dispose per liberare risorse non gestite. Inserendo il codice nell'istruzione using viene chiamato il metodo Dispose.
Darin Dimitrov,

3
Risposta terribile. Presuppone che tu possa usare un usingblocco. Sto implementando una classe che scrive di volta in volta e quindi non può.
Jez,

5
@Jez La tua classe dovrebbe quindi implementare l'interfaccia IDisposable, e possibilmente anche Close () se close è una terminologia standard nell'area , in modo che le classi che usano la tua classe possano usare using(o, ancora una volta, scegliere il modello Dispose).
Dorus,

13

La documentazione afferma che questi due metodi sono equivalenti:

StreamReader.Close : questa implementazione di Close chiama il metodo Dispose passando un valore vero.

StreamWriter.Close : questa implementazione di Close chiama il metodo Dispose passando un valore vero.

Stream.Close : questo metodo chiama Dispose, specificando true per rilasciare tutte le risorse.

Quindi, entrambi sono ugualmente validi:

/* Option 1, implicitly calling Dispose */
using (StreamWriter writer = new StreamWriter(filename)) { 
   // do something
} 

/* Option 2, explicitly calling Close */
StreamWriter writer = new StreamWriter(filename)
try {
    // do something
}
finally {
    writer.Close();
}

Personalmente, rimarrei con la prima opzione, poiché contiene meno "rumore".


5

Su molte classi che supportano entrambi Close()e Dispose()metodi, le due chiamate sarebbero equivalenti. In alcune classi, tuttavia, è possibile riaprire un oggetto che è stato chiuso. Alcune di queste classi potrebbero mantenere vive alcune risorse dopo una chiusura, al fine di consentire la riapertura; altri potrebbero non mantenere in vita alcuna risorsa Close(), ma potrebbero impostare una bandiera Dispose()per vietare esplicitamente la riapertura.

Il contratto IDisposable.Disposeprevede esplicitamente che chiamarlo su un oggetto che non verrà mai più utilizzato sarà, nel peggiore dei casi, innocuo, quindi consiglierei di chiamare uno IDisposable.Disposeo un metodo chiamato Dispose()su ogni IDisposableoggetto, anche se uno chiama o meno Close().


Cordiali saluti ecco un articolo sui blog MSDN che spiega il divertimento Chiudi e Disponi. blogs.msdn.com/b/kimhamil/archive/2008/03/15/…
JamieVedi

1

Questa è una vecchia domanda, ma ora puoi scrivere usando le istruzioni senza bisogno di bloccarle. Saranno smaltiti in ordine inverso al termine del blocco contenitore.

using var responseStream = response.GetResponseStream();
using var reader = new StreamReader(responseStream);
using var writer = new StreamWriter(filename);

int chunkSize = 1024;
while (!reader.EndOfStream)
{
    char[] buffer = new char[chunkSize];
    int count = reader.Read(buffer, 0, chunkSize);
    if (count != 0)
    {
        writer.Write(buffer, 0, count);
    }
}

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using

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.