Czy powinienem wywołać Close () czy Dispose () dla obiektów strumienia?

151

Klasy takich jak Stream, StreamReader, StreamWriteritp osprzęt IDisposableinterfejsu. Oznacza to, że możemy wywołać Dispose()metodę na obiektach tych klas. Zdefiniowali również publicmetodę o nazwie Close(). To mnie wprawia w zakłopotanie, co mam nazwać, gdy skończę z przedmiotami? A jeśli zadzwonię do obu?

Mój obecny kod to:

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

Jak widzisz, napisałem using()konstrukcje, które automatycznie wywołują Dispose()metodę na każdym obiekcie. Ale nazywam też Close()metody. Czy to jest poprawne?

Proszę zasugerować najlepsze praktyki podczas używania obiektów strumieniowych. :-)

Przykład MSDN nie używa using()konstrukcji i wywołania Close()metody:

Czy to jest dobre?

Nawaz
źródło
Jeśli używasz ReSharper, możesz zdefiniować to jako „antywzór” w katalogu wzorów. ReSharper oznaczy każde użycie jako błąd / wskazówkę / ostrzeżenie dotyczące Twojej definicji. Możliwe jest również zdefiniowanie, w jaki sposób program ReSharper ma zastosować QuickFix w takim przypadku.
Thorsten Hans
3
Tylko wskazówka: możesz użyć instrukcji using w ten sposób do wielu jednorazowych itens: using (Stream responseStream = response.GetResponseStream ()) using (StreamReader reader = new StreamReader (responseStream)) using (StreamWriter writer = new StreamWriter (filename)) {//..Some code}
Latrova
Nie musisz zagnieżdżać instrukcji using w ten sposób, że możesz je układać jedna na drugiej i mieć jeden zestaw nawiasów. W innym poście zasugerowałem edycję fragmentu kodu, który powinien był zawierać instrukcje z tą techniką, jeśli chcesz sprawdzić i naprawić swoją „strzałkę kodu”: stackoverflow.com/questions/5282999/ ...
Timothy Gonzalez
2
@ Suncat2000 Możesz mieć wiele instrukcji using, ale nie zagnieżdżaj ich i zamiast tego stosuj. Nie chodzi mi o składni takiego, który ogranicza typ: using (MemoryStream ms1 = new MemoryStream(), ms2 = new MemoryStream()) { }. Mam na myśli to w ten sposób, w którym można przedefiniować typ:using (MemoryStream ms = new MemoryStream()) using (FileStream fs = File.OpenRead("c:\\file.txt")) { }
Timothy Gonzalez,

Odpowiedzi:

101

Szybki skok do Reflector.NET pokazuje, że Close()metoda na StreamWriterto:

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

I StreamReaderjest:

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

Dispose(bool disposing)Przesłanianie w StreamReaderIS:

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

StreamWriterSposób jest podobny.

Tak więc, czytając kod, jest jasne, że możesz dzwonić Close()i korzystać Dispose()ze strumieni tak często, jak chcesz iw dowolnej kolejności. Nie zmieni to w żaden sposób zachowania.

Sprowadza się więc do tego, czy jest bardziej czytelny w użyciu Dispose(), Close()i / lub using ( ... ) { ... }.

Moje osobiste preferencje są takie, że using ( ... ) { ... }powinno się je stosować zawsze, gdy to możliwe, ponieważ pomaga to „nie biegać z nożyczkami”.

Ale chociaż pomaga to w poprawności, zmniejsza czytelność. W C # mamy już mnóstwo zamykających nawiasów klamrowych, więc skąd wiemy, który z nich faktycznie wykonuje zamknięcie w strumieniu?

Więc myślę, że najlepiej zrobić to:

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

    stream.Close();
}

Nie wpływa na zachowanie kodu, ale poprawia czytelność.

Enigmativity
źródło
20
W C # mamy już mnóstwo zamykających nawiasów klamrowych, więc skąd wiemy, który z nich faktycznie wykonuje zamknięcie w strumieniu? ” Nie sądzę, że jest to duży problem: strumień jest zamykany „we właściwym czasie”, tj. kiedy zmienna wykracza poza zakres i nie jest już potrzebna.
Heinzi,
110
Hmm, nie, to jest "dlaczego do cholery on zamyka to dwa razy?" próg zwalniający podczas czytania.
Hans Passant
57
Nie zgadzam się z niepotrzebnym Close()wezwaniem. Jeśli ktoś mniej doświadczony spojrzy na kod i nie będzie o nim wiedział using, to: 1) sprawdzi i nauczy się , lub 2) na ślepo doda Close()ręcznie. Jeśli wybierze 2), być może jakiś inny programista zobaczy zbędne Close()i zamiast „chichotać” poinstruuje mniej doświadczonego programistę. Nie jestem zwolennikiem utrudniania życia niedoświadczonym programistom, ale jestem zwolennikiem przekształcania ich w doświadczonych programistów.
R. Martinho Fernandes,
14
Jeśli użyjesz + Close () i włączysz / przeanalizujesz, otrzymasz „ostrzeżenie: CA2202: Microsoft.Usage: Obiekt 'f' można usunąć więcej niż raz w metodzie 'Foo (string)'. Aby uniknąć generowania Systemu. ObjectDisposedException nie należy wywoływać metody Dispose na obiekcie więcej niż jeden raz .: Wiersze: 41 "Tak więc, mimo że obecna implementacja jest w porządku z wywoływaniem Close i Dispose, zgodnie z dokumentacją i / analizą, nie jest to poprawne i może ulec zmianie w przyszłych wersjach. netto.
marc40000
4
+1 za dobrą odpowiedź. Kolejna rzecz do rozważenia. Dlaczego nie dodać komentarza po nawiasie zamykającym, np. // Zamknij lub tak jak ja, będąc nowicjuszem, dodaję jedną linijkę po każdym nawiasie zamykającym, który nie jest jasny. jak na przykład w długiej klasie dodałbym // End Namespace XXX po ostatnim nawiasie zamykającym i // End Class YYY po drugim ostatnim nawiasie zamykającym. Czy nie po to są komentarze. Po prostu ciekawy. :) Jako nowicjusz widziałem taki kod, więc dlaczego tu przyjechałem. Zadałem pytanie: „Po co drugie zamknięcie”. Wydaje mi się, że dodatkowe linie kodu nie zwiększają jasności. Przepraszam.
Francis Rodgers
51

Nie, nie powinieneś wywoływać tych metod ręcznie. Na końcu usingbloku Dispose()automatycznie wywoływana jest metoda, która zadba o zwolnienie niezarządzanych zasobów (przynajmniej dla standardowych klas .NET BCL, takich jak strumienie, czytniki / pisarze, ...). Możesz więc również napisać swój kod w ten sposób:

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

Close()Wywołuje metodę Dispose().

Darin Dimitrov
źródło
1
Jestem prawie pewien, że nie musisz być usingpierwszy, responseStreamponieważ jest opakowany przez, readerktóry upewni się, że zostanie zamknięty, gdy czytnik zostanie usunięty. Jednak +1
Isak Savo
To jest mylące, kiedy powiedziałeś The Close method calls Dispose.... aw pozostałej części twojego postu sugerujesz, Dispose()że zadzwoniłbym Close(), nie powinienem dzwonić do tego ostatniego ręcznie. Mówisz, że dzwonią do siebie?
Nawaz,
@Nawaz, mój post był zagmatwany. Metoda Close po prostu wywołuje Dispose. W twoim przypadku potrzebujesz Dispose, aby zwolnić niezarządzane zasoby. Zawijając kod w instrukcji using wywoływana jest metoda Dispose.
Darin Dimitrov
3
Okropna odpowiedź. Zakłada, że ​​możesz użyć usingbloku. Implementuję klasę, która pisze od czasu do czasu i dlatego nie może.
Jez,
5
@Jez Twoja klasa powinna następnie zaimplementować interfejs IDisposable, a być może także Close (), jeśli close jest standardową terminologią w tym obszarze , tak aby klasy korzystające z Twojej klasy mogły używać using(lub, ponownie, przejdź do wzorca Dispose).
Dorus
13

Dokumentacja mówi, że te dwie metody są równoważne:

StreamReader.Close : ta implementacja Close wywołuje metodę Dispose przekazującą wartość true.

StreamWriter.Close : ta implementacja Close wywołuje metodę Dispose przekazującą wartość true.

Stream.Close : ta metoda wywołuje Dispose, określając true, aby zwolnić wszystkie zasoby.

Tak więc oba są równie ważne:

/* 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();
}

Osobiście trzymałbym się pierwszej opcji, ponieważ zawiera ona mniej „szumu”.

Heinzi
źródło
5

W przypadku wielu klas, które obsługują obie Close()i Dispose()metody, te dwa wywołania byłyby równoważne. Jednak na niektórych zajęciach możliwe jest ponowne otwarcie obiektu, który został zamknięty. Niektóre takie klasy mogą utrzymywać przy życiu niektóre zasoby po Zamknięciu, aby umożliwić ich ponowne otwarcie; inni mogą nie utrzymywać żadnych zasobów przy życiu Close(), ale mogą ustawić flagę, Dispose()aby wyraźnie zabronić ponownego otwierania.

Kontrakt for IDisposable.Disposewyraźnie wymaga, aby wywołanie go na obiekcie, który nigdy nie zostanie ponownie użyty, będzie w najgorszym przypadku nieszkodliwe, dlatego zalecałbym wywołanie jednej z nich IDisposable.Disposelub metody wywoływanej Dispose()na każdym IDisposableobiekcie, niezależnie od tego, czy wywoływana jest również Close().

superkat
źródło
FYI, oto artykuł na blogach MSDN, który wyjaśnia zabawę w Close and Dispose. blogs.msdn.com/b/kimhamil/archive/2008/03/15/…
JamieZobacz
1

To jest stare pytanie, ale możesz teraz pisać przy użyciu instrukcji bez konieczności blokowania każdego z nich. Zostaną one usunięte w odwrotnej kolejności po zakończeniu bloku zawierającego.

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/propiments/csharp-8.0/using

Todd Skelton
źródło