statement - C#utilizando error de captura de declaración
using instruction c# (16)
Solo estoy mirando la declaración de uso, siempre he sabido lo que hace, pero hasta ahora no he intentado usarla, he encontrado el siguiente código:
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 funcionar, pero ¿hay algún punto en esto ya que, por lo que puedo decir, todavía tendría que incluir esto en un bloque de captura de prueba para detectar errores imprevistos, por ejemplo, el servidor sql inactivo. ¿Me estoy perdiendo de algo?
En la medida en que puedo verlo, simplemente me impide cerrar y deshacerme de cmd, pero habrá más líneas de código debido a la captura de prueba que aún se necesita.
Al elaborar lo que dijo Chris Ballance, 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 una 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 maneras 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.
Aquí hay muchas respuestas excelentes, pero no creo que esto se haya dicho todavía.
No importa qué ... se llamará al método "Dispose" en el objeto en el bloque "using". 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 una Console.Write. 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.
}
El enunciado 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 mencionó TheSoftwareJedi más arriba, 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 usar el 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.
Este código debe ser el siguiente para asegurar el cierre oportuno de la conexión. Cerrar solo 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 este controla el código muy bien y se asegura de que se acuerde de limpiar.
FYI, en este ejemplo específico, porque estás usando una conexión ADO.net y un objeto Command, ten en cuenta que la instrucción using solo ejecuta Command.Dispose, y Connection.Dispose () que en realidad no cierra la conexión, pero simplemente lo libera de nuevo en el grupo de conexiones de ADO.net para ser reutilizado por el siguiente connection.open ... lo cual es bueno, y es absolutamente correcto hacerlo, si no lo haces, la conexión permanecerá inutilizable hasta que la basura collector lo devuelve al grupo, que podría no ser 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.
No puede haber ninguna ventaja al usar una instrucción de using
en este caso si va a tener un bloque try
/ catch
/ finally
todos modos. Como saben, el enunciado de using
es azúcar sintáctica para un try
/ finally
que desecha el objeto IDisposable
. Si vas a tener tu propio try
/ finally
todos modos, sin duda puedes hacerte el Dispose
.
Esto se reduce principalmente al estilo: su equipo puede sentirse más cómodo con el using
declaraciones o el using
declaraciones puede hacer que el código se vea más limpio.
Pero, si la repetición de la declaración de using
se escondería está ahí de todos modos, adelante y maneje las cosas usted mismo si esa es su preferencia.
Sí, aún 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 sí define tus intenciones para cualquier otra persona que use tu código, y también ayuda a no dejar conexiones, etc. abiertas por error.
Si la persona que llama de su función es responsable de tratar con 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.
Si su código se ve así:
using (SqlCommand cmd = new SqlCommand(...))
{
try
{
/* call stored procedure */
}
catch (SqlException ex)
{
/* handles the exception. does not rethrow the exception */
}
}
Luego lo refactorizaría para usar try ... catch ... finally en su 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 escenario, estaría manejando la excepción, así que no tengo más remedio que agregar esa try..catch, 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.
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.
Entonces, básicamente, "usar" es exactamente lo mismo que "probar / capturar / finalmente" solo mucho más flexible para el manejo de errores.
Tomaría una decisión sobre cuándo y cuándo no usar la declaración using 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 saber que el recurso se maneja de la manera que usted desea. Como algunos ya han mencionado, la instrucción using no proporciona un manejo de excepciones, solo asegura 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.
Cuando hago trabajo de IO código codigo para esperar una excepcion.
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: para ser explícito, evito usar el bloque aquí porque creo que es importante iniciar 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.
Edición 2: Se puede argumentar que un bloque que usa 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.
Edición 3: Realmente, realmente desearía que MS hubiera creado una versión más explícita de using () que lo hiciera más intuitivo de lo que realmente estaba sucediendo y le diera más flexibilidad en este caso. Considere el siguiente código imaginario:
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 solo crea una try / finally con las llamadas a Dispose () en el finally. ¿Por qué no le da al desarrollador una forma unificada de deshacerse y manejar excepciones?
Corrección menor al ejemplo: SqlDataAdapter
también necesita ser instanciado en una instrucción de 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];
}
Primero, su 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, una excepción al crear el comando dará como resultado que la conexión recién creada no se elimine. Con lo anterior, la conexión está correctamente dispuesta.
Si necesita manejar excepciones en la construcción de la conexión y el comando (así como también al usarlas), sí, debe envolver todo el conjunto 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 es necesario que maneje la limpieza de 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
Sé cuál prefiero escribir. :-)
Un problema con "usar" es que no maneja las excepciones. si los diseñadores de "usar" agregarían "captura" opcionalmente a su sintaxis como el pseudocódigo siguiente, 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 ...
}
aún así no habrá necesidad de escribir código para deshacerse de MyDisposableObj
b / c, se manejaría using
...
¿Cómo te gusta eso?