Schließen und entsorgen - welche anrufen?

Antworten:

190

Ich möchte diese Situation klären.

Gemäß den Microsoft-Richtlinien empfiehlt es sich, gegebenenfalls eine CloseMethode bereitzustellen . Hier ist ein Zitat aus den Framework-Designrichtlinien

Erwägen Sie die Bereitstellung einer Methode Close()zusätzlich zu der Dispose()Standardterminologie in diesem Bereich, wenn close die Standardterminologie ist. Dabei ist es wichtig, dass Sie die CloseImplementierung identisch mit Dispose...

In den meisten Fällen sind Methoden Closeund DisposeMethoden gleichwertig. Der Hauptunterschied zwischen Closeund Disposeim Fall von SqlConnectionObjectist:

Eine Anwendung kann Closemehr als einmal aufrufen . Es wird keine Ausnahme generiert.

Wenn Sie die DisposeMethode aufgerufen haben, wird der SqlConnectionObjektstatus zurückgesetzt. Wenn Sie versuchen, eine Methode für ein entsorgtes SqlConnection Objekt aufzurufen , erhalten Sie eine Ausnahme.

Das gesagt:

  • Wenn Sie das Verbindungsobjekt einmal verwenden, verwenden Sie Dispose.
  • Wenn das Verbindungsobjekt wiederverwendet werden muss, verwenden Sie die CloseMethode.
aku
quelle
5
In der Dokumentation zu Close () von @Chris heißt es: "Anschließend wird die Verbindung zum Verbindungspool freigegeben oder die Verbindung geschlossen, wenn das Verbindungspooling deaktiviert ist." Daher sollte Close () ausreichen, um ein Überlaufen des Verbindungspools zu verhindern.
David Hammond
@ DavidHammond: Du hast recht. Ich lösche meinen vorherigen Kommentar.
NotMe
3
Gibt .Dispose () auch die Verbindung wieder in den Pool frei?
oscilatingcretin
Dies ist das beste Argument, das ich in einem Jahrzehnt auf die eine oder andere Weise zu diesem Thema gelesen habe. Hervorragender Punkt.
Michael Erickson
1
So funktioniert es auf diese Weise 1. con.Open() con.Close(); 2 con.Open(); // reuse 3. con.Dispose(); // use one time con.Open(); // error
Shaijut
24

Wie immer lautet die Antwort: es kommt darauf an. Verschiedene Klassen werden IDisposableauf unterschiedliche Weise implementiert , und es liegt an Ihnen, die erforderlichen Nachforschungen anzustellen.

Es wird SqlClientempfohlen, Folgendes zu tun:

using (SqlConnection conn = /* Create new instance using your favorite method */)
{
    conn.Open();
    using (SqlCommand command = /* Create new instance using your favorite method */)
    {
        // Do work
    }
    conn.Close(); // Optional
}

Sie sollten die Verbindung anrufen Dispose(oder Close*)! Warten Sie nicht , bis der Garbage Collector Ihre Verbindung bereinigt hat. Dadurch werden die Verbindungen im Pool (zumindest) bis zum nächsten GC-Zyklus gebunden. Wenn Sie aufrufen Dispose, ist ein Aufruf nicht erforderlich Close, und da das usingKonstrukt die Disposekorrekte Handhabung so einfach macht , gibt es wirklich keinen Grund zum Aufrufen Close.

Verbindungen werden automatisch zusammengefasst, und das Anrufen Dispose/ Closeauf der Verbindung schließt die Verbindung nicht physisch (unter normalen Umständen). Versuchen Sie nicht, Ihr eigenes Pooling zu implementieren. SqlClientBereinigt die Verbindung, wenn sie aus dem Pool abgerufen wird (z. B. Wiederherstellen des Datenbankkontexts und der Verbindungsoptionen).

* Wenn Sie anrufen Close, stellen Sie sicher, dass Sie dies auf ausnahmesichere Weise tun (dh in einem Catch oder schließlich Block).

Brannon
quelle
Wenn Sie sagen: "Es liegt an Ihnen, die notwendigen Nachforschungen anzustellen", was ist das für Nachforschungen? Der einzige Weg, wie ich sicher sagen kann, ist durch Reflexion, aber das hat den Nachteil, dass es in den meisten Situationen "illegal" ist.
Sturm
6
Ich würde nicht sagen: conn.Close(); // OptionalEs ist nicht optional. Es ist überflüssig und unnötig. Sie entsorgen das Objekt zweimal und dies wird von einigen Code-Analyse-Tools als Warnung markiert.
Metalogic
@Metalogic Ich bin damit einverstanden, dass es überflüssig und unnötig (und hässlich) ist, Close mit den richtigen Verwendungszwecken aufzurufen. Nitpicking: Das Aufrufen von Close bedeutet jedoch nicht "disposing" (während Dispose Close für eine SqlConnection impliziert). Vergleichen Sie mit using (var x = ..) { x.Dispose(); }, in welchem ​​Fall xwirklich "zweimal entsorgt" wird.
user2864740
11

Sie müssen Dispose () aufrufen!

Dispose () wird vom Entwickler aufgerufen, der Garbage Collector ruft Finalize () auf. Wenn Sie Dispose () für Ihre Objekte nicht aufrufen, werden nicht verwaltete Ressourcen, die sie verwendet haben, erst entsorgt, wenn der Garbage Collector vorbeikommt und sie finalisiert (und wer weiß, wann dies geschehen wird).

Dieses Szenario wird als nicht deterministische Finalisierung bezeichnet und ist eine häufige Falle für .net-Entwickler. Wenn Sie mit Objekten arbeiten, die IDisposable implementieren, rufen Sie Dispose () auf!

http://www.ondotnet.com/pub/a/oreilly/dotnet/news/programmingCsharp_0801.html?page=last

Während es viele Fälle gibt (wie bei SqlConnection), in denen Sie Disponse () für ein Objekt aufrufen und bei seiner Verbindung einfach Close () aufrufen oder ein Dateihandle schließen, ist es fast immer die beste Wahl, Dispose () aufzurufen! es sei denn, Sie planen, das Objekt in naher Zukunft wiederzuverwenden.

Tyler
quelle
26
Dieser Kommentar ist völlig falsch. Der Müllsammler ruft niemals an Dispose.
Stephen Cleary
3
Folgerung: Sie sollten aufrufen, Dispose() wenn Sie nicht using()mit einer Klasse arbeiten, die implementiert IDisposable. Wenn die aufgerufene Klasse IDisposable implementiert und Sie ihre Verwendung auf der darin enthaltenen Seite verpackt haben using(), können Sie mit dem Dispose()(Wortspiel beabsichtigt, also erschießen Sie mich) entsorgen . Die Verwendung von AFAIK Close()wird jedoch für alles empfohlen, was explizit verwendet Open()wird.
René Kåbis
Ich bin mir bei anderen DBMS nicht sicher, aber Sie können NICHT beides in PostgreSql tun . Sobald Sie Closeeine Verbindung hergestellt haben, setzt Postgres die Verbindungskennung automatisch auf null. Von da an kann man keine DisposeSQL-Verbindungskennung mehr setzen, die bereits auf gesetzt ist null.
SSD
10

Denn SqlConnectionaus der Sicht der Verbindung selbst sind sie gleichwertig. Laut Reflector werden Dispose()Aufrufe Close()sowie einige zusätzliche speicherfreie Operationen ausgeführt - hauptsächlich durch Setzen von Mitgliedern auf Null.

Für Stream sind sie tatsächlich gleichwertig. Stream.Dispose()ruft einfach Close () auf.

Curt Hagenlocher
quelle
1
Bist du sicher? MSDN sagt, dass es geerbt wurde, vonComponent dem anscheinend nichts unternommen wird, um zu versuchen, anzurufenClose() . Ich kann in keiner dieser Benachrichtigungen etwas sehen DBConnectionoder SqlConnectiondas hängt damit zusammen. Es hat jedoch eine private DisposeMe(), auf die nirgendwo verwiesen wird .
Deanna
@ Deanna es wird hier überschrieben: github.com/dotnet/corefx/blob/…
David Cumps
@ DavidCumps Es scheint, dass es sich in den 4 Jahren geändert hat, seit ich diesen Kommentar geschrieben habe. Meine Links sind nicht mehr gültig.
Deanna
6

Dieser schnelle Rat wurde zu einer langen Antwort. Es tut uns leid.

Wie Tyler in seiner netten Antwort betonte, ist das Aufrufen Dispose()eine großartige Programmierpraxis. Dies liegt daran, dass diese Methode alle erforderlichen Ressourcen freisetzen soll, damit keine nicht benötigten offenen Ressourcen vorhanden sind. Wenn Sie beispielsweise Text in eine Datei geschrieben haben und die Datei nicht schließen konnten (die Ressource freigeben), bleibt sie geöffnet, und niemand anderes kann darauf schreiben, bis der GC vorbeikommt und das tut, was Sie haben sollten getan.

In einigen Fällen wird es nun "Finalisierungs" -Methoden geben, die spezifischer für die Klasse sind, mit der Sie sich befassen, z. B. StreamWriter.Close()die überschrieben werden TextWriter.Close(). In der Tat sind sie normalerweise besser für die jeweilige Situation geeignet: Ein StreamWriter Close()beispielsweise spült den Stream und den zugrunde liegenden Encoder, bevor Dispose()das Objekt bearbeitet wird ! Cool!

Beim Durchsuchen von MSDN werden Sie jedoch feststellen, dass selbst Microsoft manchmal durch die Vielzahl von Schließern und Entsorgern verwirrt ist. Auf dieser Webseite wird zum Beispiel in einigen Beispielen Close()vor dem Impliziten aufgerufen Dispose()(siehe using-Anweisung, wenn Sie nicht verstehen, warum es implizit ist), und in einem Fall kümmern sie sich insbesondere nicht darum. Warum sollte das so sein? Auch ich war ratlos.

Der Grund, warum ich dachte (und ich betone, dies ist eine originelle Forschung und ich könnte sicherlich den Ruf verlieren, wenn ich mich irre), ist, dass dies Close()fehlschlägt und eine Ausnahme ergibt, während Ressourcen offen bleiben, während Dispose()sie sicherlich befreit werden . Weshalb ein Dispose()sollten immer einen wahren Close()Anruf (sorry für das Wortspiel).

MyResource r = new MyResource();

try {
  r.Write(new Whatever());

  r.Close()
finally {
  r.Dispose();
}

Und ja, ich denke, Microsoft ist auf dieses eine Beispiel gestoßen. Vielleicht würde dieser Zeitstempel niemals in die Datei geschrieben werden.

Ich repariere morgen meinen alten Code.

Bearbeiten: Entschuldigung Brannon, ich kann Ihre Antwort nicht kommentieren, aber sind Sie sicher, dass es eine gute Idee ist, anzurufen Close() einen finallyBlock ? Ich denke, eine Ausnahme davon könnte den Rest des Blocks ruinieren, der wahrscheinlich wichtigen Bereinigungscode enthalten würde.

Antwort an Brannon's: Großartig, vergessen Sie nicht, anzurufen, Close()wenn es wirklich benötigt wird (z. B. beim Umgang mit Streams - Sie wissen nicht viel über SQL-Verbindungen in .NET).

André Chalella
quelle
Eigentlich rufe ich niemals Close () auf, sondern lasse Dispose () und das Konstrukt 'using' das Richtige tun . Wenn Sie Dispose nicht aufrufen, müssen Sie Close ausnahmsicher aufrufen. Es ist möglicherweise eine gute Idee, dem finally-Block eine Ausnahmebehandlung hinzuzufügen.
Brannon
Richtig, meine Kommentare waren speziell für SqlClient. Der Punkt ist, dass Sie die Klassen verstehen müssen, die Sie verwenden. Immer Dispose anrufen ist nicht unbedingt die richtige Antwort.
Brannon
2

Schreiben Sie auf iDisposable und rufen Sie dazu dispose auf. Dadurch wird die Methode aufgerufen, die für die Implementierung von "iDisposable.Dispose" konfiguriert ist, unabhängig davon, wie die Funktion benannt ist.

Superkatze
quelle
Die "Funktion heißt" 'Dispose': Wir kehren also zur ersten Frage zurück:}
user2864740
Die Funktion ist gebunden IDisposable.Dispose, aber das heißt nicht, dass das der Name ist. Beachten Sie, dass es in vb.net möglich ist, eine Funktion an mehrere Schnittstellenelemente mit Namen zu binden, die nicht mit denen der Funktion verknüpft sein müssen.
Supercat
Besetzung wie using (myObj as IDisposable)
folgt
2

Im Allgemeinen haben wir Probleme mit Close (), Abort () und Dispose (), aber ich möchte Ihnen einen Unterschied zwischen ihnen erklären.

1) ABORT: - Ich werde nicht empfehlen, dies zu verwenden, da der Client beim Aufruf von abort die Verbindung löscht, ohne dies dem Server mitzuteilen, sodass der Server einige Zeit (ca. 1 Minute) warten muss. Wenn Sie eine Massenanforderung haben, können Sie abort () nicht verwenden, da dies zu einer Zeitüberschreitung für Ihren begrenzten Verbindungspool führen kann.

2) Schließen: - Schließen ist ein sehr guter Weg, um die Verbindung zu schließen, da beim Schließen der Verbindung der Server aufgerufen wird und der Server bestätigt wird, auch auf dieser Seite zu schließen.

Hier noch eine Sache zu sehen. In einigen Fällen, wenn ein Fehler generiert wird, ist es keine gute Möglichkeit, Code endgültig in diese connection.close () zu schreiben, da zu diesem Zeitpunkt der Kommunikationsstatus fehlerhaft ist.

3) Entsorgen: - Es handelt sich um eine Art des Schließens, aber nach dem Schließen der Verbindung können Sie sie nicht wieder öffnen.

Also versuche es so,

private void CloseConnection(Client client)
    {
        if (client != null && client.State == CommunicationState.Opened)
        {
            client.Close();
        }
        else
        {
            client.Abort();
        }
    }
Deep Jadia
quelle
Die Überprüfung client != nullist falsch / irreführend, da nicht alle Verwendungen geschützt sind. Ich bin mir auch nicht sicher, wie Code in den Status "Diese Verbindung ist nicht geöffnet und sollte geschlossen werden" gelangen kann.
user2864740