Soll ich Close () oder Dispose () für Stream-Objekte aufrufen?

151

Klassen wie Stream, StreamReader, StreamWriterusw. Geräte - IDisposableSchnittstelle. Das heißt, wir können anrufenDispose() Methoden für Objekte dieser Klassen . Sie haben auch eine publicMethode namens aufgerufen Close(). Das verwirrt mich, was soll ich nennen, wenn ich mit Objekten fertig bin? Was ist, wenn ich beide anrufe?

Mein aktueller Code lautet:

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

Wie Sie sehen, habe ich using()Konstrukte geschrieben , die automatisch aufrufenDispose() Methoden für jedes Objekt . Ich nenne aber auch Close()Methoden. Ist es richtig?

Bitte schlagen Sie mir die Best Practices für die Verwendung von Stream-Objekten vor. :-)

Das MSDN-Beispiel verwendet keine using()Konstrukte und ruft die Close()Methode auf:

Ist es gut?

Nawaz
quelle
Wenn Sie ReSharper verwenden, können Sie dies als "Antimuster" im Musterkatalog definieren. ReSharper markiert jede Verwendung als Fehler / Hinweis / Warnung in Bezug auf Ihre Definition. Es ist auch möglich zu definieren, wie ReSharper für ein solches Ereignis ein QuickFix anwenden muss.
Thorsten Hans
3
Nur ein Tipp: Sie können die using-Anweisung wie diese für mehrere Einweg-Itens verwenden: using (Stream responseStream = response.GetResponseStream ()) using (StreamReader reader = neuer StreamReader (responseStream)) using (StreamWriter writer = new StreamWriter (Dateiname)) {//...Einiger Code}
Latrova
Sie müssen Ihre using-Anweisungen nicht so verschachteln, dass Sie sie übereinander stapeln und einen Satz Klammern haben können. In einem anderen Beitrag schlug ich eine Bearbeitung für ein Code-Snippet vor, bei dem Anweisungen mit dieser Technik verwendet werden sollten, wenn Sie Ihren " Codepfeil
Timothy Gonzalez
2
@ Suncat2000 Sie können mehrere using-Anweisungen verwenden, diese jedoch nicht verschachteln und stattdessen stapeln. Ich meine nicht eine solche Syntax, die den Typ einschränkt : using (MemoryStream ms1 = new MemoryStream(), ms2 = new MemoryStream()) { }. Ich meine so, wo Sie den Typ neu definieren können:using (MemoryStream ms = new MemoryStream()) using (FileStream fs = File.OpenRead("c:\\file.txt")) { }
Timothy Gonzalez

Antworten:

101

Ein kurzer Sprung in Reflector.NET zeigt, dass die Close()Methode aktiviert StreamWriterist:

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

Und StreamReaderist:

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

Die Dispose(bool disposing)Überschreibung in StreamReaderist:

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

Die StreamWriterMethode ist ähnlich.

Also, den Code zu lesen ist es klar, dass , dass Sie anrufen können Close()und Dispose()auf Ströme , so oft , wie Sie möchten und in beliebiger Reihenfolge. Es wird das Verhalten in keiner Weise ändern.

Es kommt also darauf an, ob es besser lesbar Dispose()ist Close()und / und using ( ... ) { ... }.

Meine persönliche Präferenz ist, dass using ( ... ) { ... }immer verwendet werden sollte, wenn es möglich ist, da es Ihnen hilft, "nicht mit der Schere zu laufen".

Dies hilft zwar bei der Korrektheit, verringert jedoch die Lesbarkeit. In C # gibt es bereits eine Vielzahl von schließenden geschweiften Klammern. Woher wissen wir also, welche tatsächlich das Schließen des Streams ausführt?

Ich denke, es ist am besten, dies zu tun:

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

    stream.Close();
}

Es hat keinen Einfluss auf das Verhalten des Codes, verbessert jedoch die Lesbarkeit.

Rätselhaftigkeit
quelle
20
" In C # gibt es bereits eine Vielzahl von schließenden geschweiften Klammern. Woher wissen wir also, welche tatsächlich das Schließen des Streams ausführt? " Ich glaube nicht, dass dies ein großes Problem ist: Der Stream wird "zum richtigen Zeitpunkt" geschlossen. dh wenn die Variable den Gültigkeitsbereich verlässt und nicht mehr benötigt wird.
Heinzi
110
Hmm, nein, das ist ein "Warum zum Teufel schließt er es zweimal?" Speedbump beim Lesen.
Hans Passant
57
Ich bin mit dem redundanten Close()Anruf nicht einverstanden . Wenn jemand, der weniger erfahren ist, sich den Code ansieht und nichts davon weiß using, wird er: 1) ihn nachschlagen und lernen oder 2) blind einen Close()manuell hinzufügen . Wenn er 2) auswählt, sieht vielleicht ein anderer Entwickler den Redundanten Close()und weist den weniger erfahrenen Entwickler an, anstatt zu "kichern" . Ich bin nicht dafür, unerfahrenen Entwicklern das Leben schwer zu machen, aber ich bin dafür, sie zu erfahrenen Entwicklern zu machen.
R. Martinho Fernandes
14
Wenn Sie + Close () verwenden und aktivieren / analysieren, wird die Warnung "CA2202: Microsoft.Usage: Objekt 'f' kann in der Methode 'Foo (Zeichenfolge)' mehrmals entsorgt werden. Um das Generieren eines Systems zu vermeiden. ObjectDisposedException, die Sie Dispose nicht mehr als einmal für ein Objekt aufrufen sollten: Zeilen: 41 "Obwohl die aktuelle Implementierung laut Dokumentation und / oder Analyse in Ordnung ist, Close and Dispose aufzurufen, ist dies nicht in Ordnung und kann sich in zukünftigen Versionen von ändern. Netz.
März 40000
4
+1 für die gute Antwort. Eine andere Sache zu beachten. Warum nicht nach der schließenden Klammer einen Kommentar wie // Schließen hinzufügen oder wie ich als Neuling nach jeder nicht klaren schließenden Klammer einen Einzeiler hinzufüge? Wie zum Beispiel in einer langen Klasse würde ich // End Namespace XXX nach der letzten schließenden Klammer und // End Class YYY nach der zweiten letzten schließenden Klammer hinzufügen. Ist das nicht was Kommentare sind. Nur neugierig. :) Als Neuling habe ich solchen Code gesehen, hense warum ich hierher gekommen bin. Ich habe die Frage gestellt "Warum die Notwendigkeit für den zweiten Abschluss". Ich bin der Meinung, dass zusätzliche Codezeilen nicht zur Klarheit beitragen. Es tut uns leid.
Francis Rodgers
51

Nein, Sie sollten diese Methoden nicht manuell aufrufen. Am Ende des usingBlocks Dispose()wird automatisch die Methode aufgerufen, die dafür sorgt, dass nicht verwaltete Ressourcen freigegeben werden (zumindest für Standard-.NET-BCL-Klassen wie Streams, Reader / Writer, ...). Sie können Ihren Code also auch folgendermaßen schreiben:

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

Die Close()Methode ruft auf Dispose().

Darin Dimitrov
quelle
1
Ich bin mir ziemlich sicher, dass Sie nicht usingder Erste sein müssen, responseStreamda dies von dem umhüllt readerwird, der sicherstellt, dass es geschlossen ist, wenn der Leser entsorgt wird. +1 trotzdem
Isak Savo
Dies ist verwirrend, als Sie sagten The Close method calls Dispose.... und im Rest Ihres Beitrags implizieren Sie, dass Dispose()dies anrufen würde Close(), ich sollte letzteres nicht manuell anrufen. Wollen Sie damit sagen, dass sie sich gegenseitig anrufen?
Nawaz
@Nawaz, mein Beitrag war verwirrend. Die Close-Methode ruft einfach Dispose auf. In Ihrem Fall benötigen Sie Dispose, um nicht verwaltete Ressourcen freizugeben. Durch Umschließen Ihres Codes mit der Anweisung using wird die Dispose-Methode aufgerufen.
Darin Dimitrov
3
Schreckliche Antwort. Es wird davon ausgegangen, dass Sie einen usingBlock verwenden können. Ich implementiere eine Klasse, die von Zeit zu Zeit schreibt und daher nicht kann.
Jez
5
@Jez Ihre Klasse sollte dann die IDisposable-Schnittstelle und möglicherweise auch Close () implementieren, wenn close die Standardterminologie in dem Bereich ist , damit Klassen, die Ihre Klasse verwenden, diese verwenden können using(oder erneut das Dispose-Muster verwenden können).
Dorus
13

Die Dokumentation besagt, dass diese beiden Methoden gleichwertig sind:

StreamReader.Close : Diese Implementierung von Close ruft die Dispose-Methode auf, die einen wahren Wert übergibt.

StreamWriter.Close : Diese Implementierung von Close ruft die Dispose-Methode auf, die einen wahren Wert übergibt.

Stream.Close : Diese Methode ruft Dispose auf und gibt true an, um alle Ressourcen freizugeben.

Beide sind also gleichermaßen gültig:

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

Persönlich würde ich bei der ersten Option bleiben, da sie weniger "Rauschen" enthält.

Heinzi
quelle
5

In vielen Klassen, die beide Close()und Dispose()Methoden unterstützen, wären die beiden Aufrufe gleichwertig. In einigen Klassen ist es jedoch möglich, ein geschlossenes Objekt erneut zu öffnen. Einige dieser Klassen halten möglicherweise einige Ressourcen nach einem Abschluss am Leben, um ein erneutes Öffnen zu ermöglichen. Andere halten möglicherweise keine Ressourcen am Leben Close(), setzen jedoch möglicherweise ein Flag, Dispose()um das erneute Öffnen explizit zu verbieten.

Der Vertrag für IDisposable.Disposeverlangt ausdrücklich, dass das Aufrufen eines Objekts, das nie wieder verwendet wird, im schlimmsten Fall harmlos ist. Daher würde ich empfehlen, entweder IDisposable.Disposeoder eine Methode aufzurufen , die Dispose()für jedes IDisposableObjekt aufgerufen wird , unabhängig davon, ob eines auch aufgerufen wird oder nicht Close().

Superkatze
quelle
Zu Ihrer Information, hier ist ein Artikel in den MSDN-Blogs, der den Spaß beim Schließen und Entsorgen erklärt. blogs.msdn.com/b/kimhamil/archive/2008/03/15/…
JamieSee
1

Dies ist eine alte Frage, aber Sie können jetzt mit Anweisungen schreiben, ohne jede einzelne blockieren zu müssen. Sie werden in umgekehrter Reihenfolge entsorgt, wenn der enthaltende Block fertig ist.

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

Todd Skelton
quelle