2008-10-30 16 views
28

estoy sola observación de la instrucción using, siempre he sabido lo que hace, pero hasta ahora no se ha intentado usar, me han llegado con el código de abajo:C# error de captura using

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

Esto parece para trabajar, pero ¿hay algún punto en esto ya que, por lo que puedo decir, todavía tendría que incluir esto en un bloque try catch para atrapar errores imprevistos, por ejemplo, sql servidor abajo. ¿Me estoy perdiendo de algo?

En la medida en que puedo verlo, simplemente me impide cerrar y eliminar el cmd, pero aún habrá más líneas de código debido a la captura de prueba.

Respuesta

18

Este código debe ser el siguiente para garantizar el cierre oportuno de la conexión. Cerrando simplemente el comando no cierra la conexión:

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

Para responder a su pregunta, puede hacer lo mismo en un bloque finally, pero esto alcances muy bien el código y asegura que recuerde que limpiar.

+2

No dijo nada sobre la captura de errores imprevistos con la conexión de db ... –

4

usar no se trata de atrapar excepciones. Se trata de deshacerse de los recursos que están fuera de la vista del recolector de basura.

+2

Veo lo que dice pero no puedo ver una ventaja sobre un bloque try catch finally con declaraciones close y dispose. – PeteT

+2

Los recursos pueden no estar fuera de la vista del recolector de basura. Todavía es útil limpiarlos lo antes posible en lugar de esperar a GC. –

+1

tampoco se trata del recolector de basura. –

2

Sí, todavía necesitaría detectar excepciones. El beneficio del bloque de uso es que está agregando alcance a su código. Usted está diciendo: "Dentro de este bloque de código haga algunas cosas y cuando llegue al final, cierre y elimine los recursos"

No es completamente necesario en absoluto, pero define sus intenciones para cualquier otra persona que use su código , y también ayuda a no dejar conexiones, etc. abiertas por error.

57

Al hacer IO trabajo código a esperar una excepción.

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

Editar: ser explícitos, evito el uso de bloque de aquí porque creo que es importante para iniciar la sesión en situaciones como esta. La experiencia me ha enseñado que nunca se sabe qué tipo de excepción extraña podría aparecer. Iniciar sesión en esta situación puede ayudarlo a detectar un punto muerto, o encontrar dónde un cambio de esquema está afectando a una parte poco usada y poco probada de su código base, o cualquier cantidad de otros problemas.

Editar 2: Se puede argumentar que un bloque de uso podría envolver una trampa/captura en esta situación, y esto es completamente válido y funcionalmente equivalente. Esto realmente se reduce a la preferencia. ¿Desea evitar la anidación adicional a costa de manejar su propia eliminación? O incurre en el anidamiento adicional para tener la eliminación automática. Siento que el primero es más limpio, así que lo hago de esa manera. Sin embargo, no reescribo este último si lo encuentro en la base de código en la que estoy trabajando.

Edit 3: Realmente, realmente me hubiera gustado que MS hubiera creado una versión más explícita de using() que hiciera más intuitivo lo que realmente estaba pasando y diera más flexibilidad en este caso. Considere lo siguiente, el código imaginaria:

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

Una instrucción using simplemente crea un try/finally con Dispose() llama en el fin. ¿Por qué no le da al desarrollador una forma unificada de deshacerse y manejar excepciones?

+0

¿Por qué la eliminación explícita, cuando el bloque de uso hace exactamente lo mismo mucho más elegante? – yfeldblum

+3

Entonces puedo registrar la excepción. No hay nada elegante en una excepción de tiempo de ejecución en el escritorio de un usuario en una parte remota del país/mundo, y usted no tiene idea de qué fue lo que salió mal. –

+3

Pero la eliminación explícita versus el uso de 'usar' no tiene ningún efecto sobre si/cómo se registran las excepciones. Puede usar fácilmente el bloque 'using' y, dentro del bloque 'using', tener un bloque try en el que se realiza la operación y un bloque catch para registrar excepciones. – yfeldblum

1

La sentencia using se convierte realmente en un bloque try/finally por el compilador en el que se elimina el parámetro del bloque using siempre que implemente la interfaz IDisposable. Además de garantizar que los objetos especificados se eliminen adecuadamente cuando se salgan del alcance, realmente no se captura ningún error al usar esta construcción.

Como se menciona en TheSoftwareJedi anterior, querrá asegurarse de que los objetos SqlConnection y SqlCommand se eliminen correctamente. Apilar ambos en un solo bloque de uso es un poco complicado, y puede que no haga lo que crees que hace.

Además, tenga en cuenta el uso del bloque try/catch como lógica. Es un olor codicioso que a mi nariz le desagrada en particular, y que a menudo utilizan los novatos o los que tenemos prisa para cumplir con un plazo.

1

FYI, en este ejemplo específico, porque está utilizando una conexión ADO.net y un objeto Command, tenga en cuenta que la instrucción using solo ejecuta Command.Dispose, y Connection.Dispose() que en realidad no se cierra la conexión, pero simplemente la libera de nuevo en el grupo de conexiones de ADO.net para ser reutilizada por la siguiente conexión.abierta ... lo cual es bueno, y es absolutamente correcto hacerlo, si no lo haces, la conexión permanecerá no se puede usar hasta que el recolector de basura lo devuelve al grupo, lo que podría no ocurrir hasta otras numerosas solicitudes de conexión, que de lo contrario se verían obligadas a crear nuevas conexiones, aunque haya una que no se esté usando esperando a ser recogida de basura.

5

Al elaborar lo que Chris Ballance dijo, la especificación de C# (ECMA-334 versión 4) sección 15.13 establece "Una declaración de uso se traduce en tres partes: adquisición, uso y eliminación. El uso del recurso está implícitamente encerrado en declaración try que incluye una cláusula finally. Esta cláusula finally dispone del recurso. Si se adquiere un recurso nulo, no se realiza ninguna llamada a Dispose y no se lanza ninguna excepción ".

La descripción está cerca de 2 páginas, vale la pena leerla.

En mi experiencia, SqlConnection/SqlCommand puede generar errores de tantas formas que casi necesita manejar las excepciones lanzadas más que manejar el comportamiento esperado. No estoy seguro de querer utilizar la cláusula de uso aquí, ya que me gustaría poder manejar el caso de recurso nulo por mi cuenta.

2

Aquí hay muchas respuestas excelentes, pero no creo que esto se haya dicho todavía.

No importa qué ... se llamará al método "Eliminar" en el objeto en el bloque "usar". Si coloca una declaración de devolución, o arroja un error, se llamará a "Dispose".

Ejemplo:

hice una clase llamada "MyDisposable", e implementa IDisposable y simplemente hace un Console.Write. Se siempre escribe en la consola incluso en todos estos escenarios:

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

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

Si el código es el siguiente:

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

entonces yo refactorearlo tratar de usar .. .. captura por último lugar .

SqlCommand cmd = new SqlCommand(...) 
try 
{ 
    /* call stored procedure */ 
} 
catch (SqlException ex) 
{ 
    /* handles the exception and does not ignore it */ 
} 
finally 
{ 
    if (cmd!=null) cmd.Dispose(); 
} 

En este caso, estaría manejando la excepción, así que no tengo más remedio que agregar ese intento.atrapar, también podría poner la cláusula finally y ahorrarme otro nivel de anidación. Tenga en cuenta que debo estar haciendo algo en el bloque catch y no solo ignorar la excepción.

0

Si la persona que llama de su función es responsable de manejar cualquier excepción, la declaración de uso es una buena manera de asegurar que los recursos se limpien sin importar el resultado.

Le permite colocar el código de manejo de excepciones en los límites de capa/ensamblaje y ayuda a evitar que otras funciones se llenen demasiado.

Por supuesto, realmente depende de los tipos de excepciones arrojadas por su código. Algunas veces deberías usar try-catch-finally en lugar de usar una declaración. Mi costumbre es comenzar siempre con una declaración using para IDisposables (o las clases que contienen IDisposables también implementan la interfaz) y agregar try-catch-finally según sea necesario.

14

Puede que no haya ventaja de utilizar una declaración using en este caso si usted va a tener un bloque try/catch/finally de todos modos. Como sabe, la declaración using es azúcar sintáctica para un try/finally que elimina el objeto IDisposable. Si va a tener su propio try/finally de todos modos, ciertamente puede hacer el Dispose usted mismo.

Esto se reduce principalmente al estilo: su equipo puede sentirse más cómodo con las declaraciones using o las declaraciones using pueden hacer que el código se vea más limpio.

Pero, si la repetición de la declaración using se estaría ocultando de todos modos, siga adelante y maneje las cosas usted mismo si esa es su preferencia.

0

Así que, básicamente, "usar" es exactamente lo mismo que "probar/capturar/finalmente" solo mucho más flexible para el manejo de errores.

+1

No, el uso es azúcar sintáctico para una declaración "try/finally". A menos que envuelva la declaración en una nueva prueba/captura o anide una en su interior, no puede detectar ninguna excepción lanzada; algo que encuentro arriesgado cuando interactúo con recursos no administrados. –

+0

Eso no es cierto, no es una forma elegante de reemplazar try/catch/finally, porque no proporciona la manera de "atrapar" excepciones. Usando == Try/Finally (sin la captura). Si aún necesita capturar la excepción y hacer un procesamiento personalizado, debe envolver/anidar el uso con el bloque try/catch, o reemplazarlo completamente con try/catch/finally. – devfreak

0

pequeña corrección al ejemplo: SqlDataAdapter necesita también deberían ejecutarse en un comunicado using:

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

un problema con el "uso" es que no maneja excepciones. si los diseñadores de "usar" añadiría "atrapar" opcionalmente a su sintaxis como la siguiente pseudocódigo, sería mucho más útil:

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 ... 

} 

todavía no habrá necesidad de escribir código para disponer de MyDisposableObj b/c sería manejado por using ...

¿Qué le parece?

1

Tomaría una decisión sobre cuándo y cuándo no usar la instrucción de uso dependiente del recurso con el que estoy tratando. En el caso de un recurso limitado, como una conexión ODBC, preferiría usar T/C/F para poder registrar errores significativos en el momento en que ocurrieron. Permitir que los errores del controlador de la base de datos regresen al cliente y se pierdan en el envoltorio de excepciones de nivel superior es subóptimo.

T/C/F le da la tranquilidad de que el recurso se maneja de la manera que usted desea. Como algunos ya han mencionado, la instrucción de uso no proporciona una gestión de excepciones, solo garantiza que el recurso se destruya. El manejo de excepciones es una estructura de lenguaje subestimada y subestimada que a menudo es la diferencia entre el éxito y el fracaso de una solución.

0

En primer lugar, el ejemplo de código debe ser:

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

Con el código en su pregunta, no estando dispuesta una excepción creación de la orden dará lugar a la conexión que acaba de crear. Con lo anterior, la conexión está correctamente dispuesta.

Si usted necesita para manejar excepciones en construcción de la conexión y de mando (así como la hora de utilizarlos), sí, hay que envolver toda la cosa en un try/catch:

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... 
} 

Pero no necesita manejar la limpieza conn o cmd; ya está hecho para ti.

contraste con lo mismo sin 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 

Yo sé a qué Prefiero escribir. :-)