2008-10-30 6 views
28

ich an der using-Anweisung nur suchen, habe ich immer gewusst, was es tut, aber bis jetzt benutze es nicht versucht, ich habe mit dem Code unten kommen:C# using-Anweisung fangen Fehler

using (SqlCommand cmd = 
    new SqlCommand(reportDataSource, 
     new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))) 
{ 
    cmd.CommandType = CommandType.StoredProcedure; 
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year; 
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start; 
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end; 
    cmd.Connection.Open(); 

    DataSet dset = new DataSet(); 
    new SqlDataAdapter(cmd).Fill(dset); 
    this.gridDataSource.DataSource = dset.Tables[0]; 
} 

Dies scheint zu arbeiten, aber gibt es irgendeinen Punkt in diesem, da, soweit ich sagen kann, ich es noch in einen Versuch fangenblock einschließen müsste, um unvorhergesehene Fehler zB zu fangen SQL Server heruntergefahren. Fehle ich etwas?

So weit ich es derzeit sehen kann, stoppt es nur das Schließen und Entsorgen von cmd, aber es wird mehr Codezeilen geben, da der Try Catch immer noch benötigt wird.

Antwort

18

Dieser Code sollte wie folgt aussehen, um den rechtzeitigen Abschluss der Verbindung sicherzustellen. nur den Befehl Schließen schließt die Verbindung nicht:

using (SqlConnection con = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString)) 
using (SqlCommand cmd = new SqlCommand(reportDataSource, con)) 
     { 
      cmd.CommandType = CommandType.StoredProcedure; 
      cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year; 
      cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start; 
      cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end; 
      cmd.Connection.Open(); 

      DataSet dset = new DataSet(); 
      new SqlDataAdapter(cmd).Fill(dset); 
      this.gridDataSource.DataSource = dset.Tables[0]; 
     } 

Ihre Frage zu beantworten, die Sie in einem finally-Block das gleiche tun, aber diese Tive den Code schön und sorgt dafür, dass Sie zu bereinigen erinnern.

+2

Sie haben nichts über das Erfassen von unvorhergesehenen Fehlern mit db-Verbindung erzählt ... –

4

Verwendung ist nicht zum Abfangen von Ausnahmen. Es geht darum, Ressourcen ordnungsgemäß zu entsorgen, die außerhalb der Sicht des Garbage Collectors liegen.

+2

Ich sehe, was Sie sagen, aber ich kann nicht einen Vorteil gegenüber einem Versuch zu sehen, endlich Block mit Schließen und Entsorgen Aussagen zu blockieren. – PeteT

+2

Ressourcen dürfen nicht außerhalb der Sicht des Garbage Collectors liegen. Es ist immer noch hilfreich, sie so schnell wie möglich zu reinigen, anstatt auf GC zu warten. –

+1

geht es auch nicht um den Garbage Collector. –

2

Ja, Sie müssten immer noch Ausnahmen abfangen. Der Vorteil des using-Blocks besteht darin, dass Sie Ihrem Code einen Bereich hinzufügen. Sie sagen: "In diesem Block Code einige Dinge tun und wenn es zu Ende kommt, schließen und Ressourcen entsorgen"

Es ist überhaupt nicht völlig notwendig, aber es definiert Ihre Absichten für alle anderen mit Ihrem Code , und es hilft auch, Verbindungen, die aus Versehen geöffnet wurden, nicht zu verlassen.

57

Bei der IO-Arbeit Code ich eine Ausnahme erwarten.

SqlConnection conn = null; 
SqlCommand cmd = null; 

try 
{ 
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString) 
    cmd = new SqlCommand(reportDataSource, conn); 
    cmd.CommandType = CommandType.StoredProcedure; 
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year; 
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start; 
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end; 

     conn.Open(); //opens connection 

    DataSet dset = new DataSet(); 
    new SqlDataAdapter(cmd).Fill(dset); 
    this.gridDataSource.DataSource = dset.Tables[0]; 
} 
catch(Exception ex) 
{ 
    Logger.Log(ex); 
    throw; 
} 
finally 
{ 
    if(conn != null) 
     conn.Dispose(); 

     if(cmd != null) 
     cmd.Dispose(); 
} 

Edit: explizit sein, ich vermeiden, die mit Block hier, weil ich es für wichtig halte, um in Situationen wie diese zu protokollieren. Die Erfahrung hat mich gelehrt, dass man nie weiß, welche seltsame Ausnahme auftauchen könnte. Wenn Sie diese Situation protokollieren, können Sie möglicherweise einen Deadlock erkennen oder herausfinden, wo sich eine Änderung des Schemas auf einen wenig genutzten und wenig getesteten Teil Ihrer Codebasis oder auf eine beliebige Anzahl anderer Probleme auswirkt.

Edit 2: Man kann argumentieren, dass ein using-Block eine try/catch in dieser Situation wickeln könnte, und das ist vollständig gültig und funktionell gleichwertig. Dies läuft auf die Präferenz hinaus. Möchten Sie die zusätzliche Verschachtelung auf Kosten der eigenen Entsorgung vermeiden? Oder erleiden Sie die zusätzliche Verschachtelung, um automatisch entsorgt zu werden. Ich fühle, dass Ersteres sauberer ist, also mache ich es so. Allerdings schreibe ich das letztere nicht neu, wenn ich es in der Code-Basis finde, in der ich arbeite.

Edit 3: Ich wünschte wirklich, MS hätte eine explizitere Version von using() erstellt, die es intuitiver machte, was wirklich passierte, und in diesem Fall mehr Flexibilität erhielt. Betrachten Sie den folgenden, imaginären Code:

SqlConnection conn = null; 
SqlCommand cmd = null; 

using(conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString), 
      cmd = new SqlCommand(reportDataSource, conn) 
{ 
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString); 
    cmd = new SqlCommand(reportDataSource, conn); 
    cmd.CommandType = CommandType.StoredProcedure; 
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year; 
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start; 
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end; 
     cmd.Open(); 

    DataSet dset = new DataSet(); 
    new SqlDataAdapter(cmd).Fill(dset); 
    this.gridDataSource.DataSource = dset.Tables[0]; 
} 
catch(Exception ex) 
{ 
    Logger.Log(ex); 
    throw; 
} 

A mit Anweisung erstellt nur ein try/finally mit Dispose() ruft in der schließlich. Warum nicht dem Entwickler eine einheitliche Möglichkeit geben, die Entsorgung und die Ausnahmebehandlung zu erledigen?

+0

Warum die explizite Beseitigung, wenn der using-Block die gleiche Sache weit eleganter macht? – yfeldblum

+3

So kann ich die Ausnahme protokollieren. Es gibt nichts Elegantes in einer Laufzeitausnahme auf dem Desktop eines Benutzers in einem abgelegenen Teil des Landes/der Welt, und Sie haben keine Ahnung, was schief gelaufen ist. –

+3

Aber die explizite Beseitigung vs Using hat absolut keinen Einfluss darauf, ob/wie Sie Ausnahmen protokollieren. Sie können den Block 'using' einfach verwenden und innerhalb des Blocks 'using' einen try-Block, in dem die Operation ausgeführt wird, und einen catch-Block zum Protokollieren von Ausnahmen haben. – yfeldblum

1

Die using-Anweisung wird tatsächlich vom Compiler in einen try/finally-Block umgewandelt, in dem der Parameter des using-Blocks entsorgt wird, solange er die IDisposable-Schnittstelle implementiert. Abgesehen davon, dass sichergestellt wird, dass die spezifizierten Objekte ordnungsgemäß entsorgt werden, wenn sie außerhalb des Geltungsbereiches liegen, gibt es wirklich keine Fehlererfassung, die durch Verwendung dieses Konstrukts erhalten wird.

Wie oben unter TheSoftwareJedi erwähnt, sollten Sie sicherstellen, dass sowohl die SqlConnection- als auch die SqlCommand-Objekte ordnungsgemäß entsorgt werden. Das Stapeln von beiden in einen einzelnen Benutzungsblock ist ein bisschen unordentlich und könnte nicht tun, was Sie denken, dass es tut.

Denken Sie auch daran, den try/catch-Block als Logik zu verwenden. Es ist ein Code-Geruch, den meine Nase besonders ablehnt und oft von Neulingen oder von uns in großer Eile benutzt wird, um einen Termin einzuhalten.

1

In diesem speziellen Beispiel, da Sie eine ADO.net-Verbindung und ein Command-Objekt verwenden, beachten Sie, dass die using-Anweisung nur die Befehle Command.Dispose und Connection.Dispose() ausführt, die nicht wirklich geschlossen werden Die Verbindung wird jedoch einfach wieder in den ADO.net Connection-Pool freigegeben, damit sie von der nächsten connection.open wiederverwendet werden kann. Das ist gut und die absolut richtige Sache, bc wenn nicht, bleibt die Verbindung bestehen Unbrauchbar, bis der Garbage Collector es wieder in den Pool zurückgibt, was möglicherweise erst nach zahlreichen anderen Verbindungsanforderungen erfolgt, die andernfalls gezwungen wären, neue Verbindungen zu erstellen, obwohl ein unbenutzter Container auf Garbage Collection wartet.

5

Ausarbeiten über das, was Chris Ballance sagte, die C# -Spezifikation (ECMA-334 Version 4) Abschnitt 15.13 besagt "Eine using-Anweisung wird in drei Teile übersetzt: Erwerb, Nutzung und Entsorgung try-Anweisung, die eine finally-Klausel enthält. Diese finally-Klausel verfügt über die Ressource. Wenn eine Null-Ressource erworben wird, wird kein Aufruf an Dispose vorgenommen, und es wird keine Ausnahme ausgelöst. "

Die Beschreibung ist in der Nähe von 2 Seiten - eine Lektüre wert.

Meiner Erfahrung nach kann SqlConnection/SqlCommand auf so viele Arten Fehler erzeugen, dass Sie die Ausnahmen, die ausgelöst werden, fast bewältigen müssen, um das erwartete Verhalten zu bewältigen. Ich bin mir nicht sicher, ob ich die using-Klausel hier haben möchte, da ich selbst den Fall mit der Null-Ressource behandeln könnte.

2

Es gibt viele gute Antworten hier, aber ich denke nicht, dass das schon gesagt wurde.

Egal was ... die Methode "Dispose" wird für das Objekt im Block "using" aufgerufen. Wenn Sie eine return-Anweisung eingeben oder einen Fehler ausgeben, wird "Dispose" aufgerufen.

Beispiel:

ich eine Klasse gemacht "MyDisposable" genannt, und es IDisposable implementiert und einfach macht einen Console.Write. Es immer schreibt an die Konsole auch in all diesen Szenarien:

using (MyDisposable blah = new MyDisposable()) 
{ 
    int.Parse("!"); // <- calls "Dispose" after the error. 

    return; // <-- calls Dispose before returning. 
} 
6

Wenn Ihr Code wie folgt aussieht:

using (SqlCommand cmd = new SqlCommand(...)) 
{ 
    try 
    { 
    /* call stored procedure */ 
    } 
    catch (SqlException ex) 
    { 
    /* handles the exception. does not rethrow the exception */ 
    } 
} 

Dann würde ich es Refactoring versuchen zu verwenden .. catch .. endlich statt .

In diesem Szenario würde ich die Ausnahme behandeln, so dass ich keine andere Wahl habe, als in diesem Versuch hinzuzufügen ..Fang, ich könnte genauso gut die Endgültigkeitsklausel setzen und mir eine weitere Verschachtelungsebene sparen. Beachten Sie, dass ich im catch-Block etwas tun muss und nicht nur die Ausnahme ignorieren muss.

0

Wenn der Aufrufer Ihrer Funktion für die Behandlung von Ausnahmen zuständig ist, ist die using-Anweisung eine gute Möglichkeit, sicherzustellen, dass Ressourcen unabhängig vom Ergebnis bereinigt werden.

Sie können Ausnahmebehandlungscode an Layer-/Assembly-Grenzen platzieren und verhindern, dass andere Funktionen zu unübersichtlich werden.

Natürlich hängt es wirklich von den Arten von Ausnahmen ab, die von Ihrem Code ausgelöst werden. Manchmal sollten Sie try-catch-finally anstelle einer using-Anweisung verwenden. Meine Angewohnheit ist, immer mit einer using-Anweisung für IDisposables zu beginnen (oder Klassen, die IDisposables enthalten, auch die Schnittstelle zu implementieren) und nach Bedarf try-catch-finally hinzuzufügen.

14

Es kann kein Vorteil sein, eine using Aussage in diesem Fall zu verwenden, wenn Sie sowieso einen try/catch/finally Block haben werden. Wie Sie wissen, ist die using-Anweisung syntaktischer Zucker für eine try/finally, die über das IDisposable-Objekt verfügt. Wenn Sie sowieso Ihre eigene try/finally haben, können Sie sicherlich die Dispose selbst tun.

Dies ist eigentlich hauptsächlich auf Stil - Ihr Team kann mit using Aussagen oder using Anweisungen bequemer sein kann den Code sauberer aussehen.

Aber, wenn die Boilerplate die using Anweisung wäre, ist dort sowieso, gehen Sie voran und behandeln Sie die Dinge selbst, wenn das Ihre Präferenz ist.

0

Also, im Grunde ist "verwenden" genau das gleiche wie "Try/catch/finally" nur viel flexibler für die Fehlerbehandlung.

+1

Nein, Using ist syntaktischer Zucker für eine "try/finally" Aussage. Wenn Sie die Anweisung nicht in ein weiteres try/catch-Element einbetten oder ein Element darin verschachteln, können Sie keine ausgelösten Ausnahmen abfangen. etwas, das ich bei der Interaktion mit nicht verwalteten Ressourcen als riskant empfinde. –

+0

Das ist nicht wahr, die Verwendung ist keine raffinierte Möglichkeit, try/catch/finally zu ersetzen, weil es Ihnen nicht die Möglichkeit bietet, Ausnahmen zu "fangen". Mit == Versuchen/Endlich (ohne den Haken). Wenn Sie die Ausnahme noch abfangen und eine benutzerdefinierte Verarbeitung vornehmen müssen, müssen Sie die Verwendung mit try/catch blockieren/verschachteln oder vollständig durch try/catch/finally ersetzen. – devfreak

0

Minor Korrektur zum Beispiel: SqlDataAdapter muss auch in einer using Anweisung instanziiert werden:

using (SqlConnection con = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString)) 
using (SqlCommand cmd = new SqlCommand(reportDataSource, con)) 
{ 
    cmd.CommandType = CommandType.StoredProcedure; 
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year; 
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start; 
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end; 
    con.Open(); 

    DataSet dset = new DataSet(); 
    using (SqlDataAdapter adapter = new SqlDataAdapter(cmd)) 
    { 
     adapter.Fill(dset); 
    } 
    this.gridDataSource.DataSource = dset.Tables[0]; 
} 
4

ein Problem mit der Angabe „“ ist, dass es keine Ausnahmen nicht behandelt. wenn die Designer von „using“ zu seiner Syntax hinzufügen würde „fangen“ gegebenenfalls wie unter Pseudo-Code, wäre es viel sinnvoller sein:

using (...MyDisposableObj...) 
{ 

    ... use MyDisposableObj ... 

catch (exception) 

    ... handle exception ... 

} 

it could even have an optional "finally" clause to cleanup anything other than the "MyDisposableObj" allocated at the beginning of the "using" statement... like: 

using (...MyDisposableObj...) 
{ 

    ... use MyDisposableObj ... 
    ... open a file or db connection ... 

catch (exception) 

    ... handle exception ... 

finally 

    ... close the file or db connection ... 

} 

noch da würde keine Notwendigkeit, Code zu schreiben, die MyDisposableObj b zu entsorgen/c würde es gehandhabt werden durch using ...

Wie man das mag?

1

Ich würde meine Entscheidung treffen, wann und wann die using-Anweisung nicht abhängig von der Ressource, die ich habe, zu verwenden. Im Falle einer begrenzten Ressource, wie einer ODBC-Verbindung, würde ich lieber T/C/F verwenden, um sinnvolle Fehler an dem Punkt zu protokollieren, an dem sie aufgetreten sind. Es ist suboptimal, Fehler des Datenbanktreibers zum Client zurückzuleiten und möglicherweise in der übergeordneten Wrapper-Wrapper-Schleife verloren zu gehen.

T/C/F gibt Ihnen die Gewissheit, dass die Ressource so gehandhabt wird, wie Sie es möchten. Wie einige bereits erwähnt haben, stellt die using-Anweisung keine Ausnahmebehandlung bereit, sondern stellt lediglich sicher, dass die Ressource zerstört wird. Exception-Handling ist eine wenig hilfreiche und unterschätzte Sprachstruktur, die oft den Unterschied zwischen Erfolg und Misserfolg einer Lösung ausmacht.

0

Zuerst Ihr Codebeispiel sein sollte:

using (SqlConnection conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString)) 
using (SqlCommand cmd = new SqlCommand(reportDataSource, conn)) 
{ 
    cmd.CommandType = CommandType.StoredProcedure; 
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year; 
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start; 
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end; 
    cmd.Connection.Open(); 

    DataSet dset = new DataSet(); 
    new SqlDataAdapter(cmd).Fill(dset); 
    this.gridDataSource.DataSource = dset.Tables[0]; 
} 

Mit dem Code in Ihrer Frage, eine Ausnahme den Befehl zu schaffen, in der soeben erstellte Verbindung führt nicht angeordnet ist. Mit dem Obigen ist die Verbindung richtig angeordnet.

Wenn Sie Ausnahmen in Bau behandeln müssen der Verbindung und Befehl (wie auch, wenn mit ihnen), ja, Sie haben die ganze Sache in einem try/catch wickeln:

try 
{ 
    using (SqlConnection conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString)) 
    using (SqlCommand cmd = new SqlCommand(reportDataSource, conn)) 
    { 
     cmd.CommandType = CommandType.StoredProcedure; 
     cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year; 
     cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start; 
     cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end; 
     cmd.Connection.Open(); 

     DataSet dset = new DataSet(); 
     new SqlDataAdapter(cmd).Fill(dset); 
     this.gridDataSource.DataSource = dset.Tables[0]; 
    } 
} 
catch (RelevantException ex) 
{ 
    // ...handling... 
} 

Aber Sie müssen nicht aufräumen oder cmd; es ist schon für dich gemacht worden.

Kontrast mit der gleichen Sache ohne using:

SqlConnection conn = null; 
SqlCommand cmd = null; 
try 
{ 
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString); 
    cmd = new SqlCommand(reportDataSource, conn); 
    cmd.CommandType = CommandType.StoredProcedure; 
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year; 
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start; 
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end; 
    cmd.Connection.Open(); 

    DataSet dset = new DataSet(); 
    new SqlDataAdapter(cmd).Fill(dset); 
    this.gridDataSource.DataSource = dset.Tables[0]; 
} 
catch (RelevantException ex) 
{ 
    // ...handling... 
} 
finally 
{ 
    if (cmd != null) 
    { 
     try 
     { 
      cmd.Dispose(); 
     } 
     catch { } 
     cmd = null; 
    } 
    if (conn != null) 
    { 
     try 
     { 
      conn.Dispose(); 
     } 
     catch { } 
     conn = null; 
    } 
} 
// And note that `cmd` and `conn` are still in scope here, even though they're useless 

Ich weiß, was ich lieber schreiben. :-)