try - ¿Por qué atrapar y volver a lanzar una excepción en C#?
try catch finally c# ejemplos (16)
Estoy mirando el artículo C # - Objeto de transferencia de datos en DTOs serializables.
El artículo incluye esta pieza de código:
public static string SerializeDTO(DTO dto) {
try {
XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
StringWriter sWriter = new StringWriter();
xmlSer.Serialize(sWriter, dto);
return sWriter.ToString();
}
catch(Exception ex) {
throw ex;
}
}
El resto del artículo parece sensato y razonable (para un noob), pero ese try-catch-throw arroja una WtfException ... ¿No es esto exactamente equivalente a no manejar las excepciones?
Es decir:
public static string SerializeDTO(DTO dto) {
XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
StringWriter sWriter = new StringWriter();
xmlSer.Serialize(sWriter, dto);
return sWriter.ToString();
}
¿O me falta algo fundamental sobre el manejo de errores en C #? Es casi lo mismo que Java (menos las excepciones verificadas), ¿no es así? ... Es decir, ambos refinaron C ++.
La pregunta de desbordamiento de pila ¿ La diferencia entre volver a lanzar la captura sin parámetros y no hacer nada? parece apoyar mi argumento de que try-catch-throw is-a-no-op.
EDITAR:
Solo para resumir para cualquiera que encuentre este hilo en el futuro ...
NO HAGA
try {
// Do stuff that might throw an exception
}
catch (Exception e) {
throw e; // This destroys the strack trace information!
}
¡La información de seguimiento de la pila puede ser crucial para identificar la causa raíz del problema!
HACER
try {
// Do stuff that might throw an exception
}
catch (SqlException e) {
// Log it
if (e.ErrorCode != NO_ROW_ERROR) { // filter out NoDataFound.
// Do special cleanup, like maybe closing the "dirty" database connection.
throw; // This preserves the stack trace
}
}
catch (IOException e) {
// Log it
throw;
}
catch (Exception e) {
// Log it
throw new DAOException("Excrement occurred", e); // wrapped & chained exceptions (just like java).
}
finally {
// Normal clean goes here (like closing open files).
}
Capte las excepciones más específicas antes que las menos específicas (como Java).
Referencias:
¿No es esto exactamente equivalente a no manejar las excepciones?
No exactamente, no es lo mismo. Se restablece el stacktrace de la excepción. Aunque estoy de acuerdo en que esto probablemente sea un error y, por lo tanto, un ejemplo de código incorrecto.
Además de lo que han dicho los demás, vea mi respuesta a una pregunta relacionada que muestra que capturar y volver a generar no es un no-op (está en VB, pero parte del código puede ser C # invocado desde VB).
C # (antes de C # 6) no admite las "excepciones filtradas" de CIL, lo que hace VB, por lo que en C # 1-5 una razón para volver a lanzar una excepción es que no tiene suficiente información en el momento de la captura () para determinar si realmente quería atrapar la excepción.
Por ejemplo, en VB puedes hacer
Try
..
Catch Ex As MyException When Ex.ErrorCode = 123
..
End Try
... que no manejaría MyExceptions con diferentes valores de ErrorCode. En C # antes de v6, tendría que atrapar y volver a lanzar MyException si el ErrorCode no era 123:
try
{
...
}
catch(MyException ex)
{
if (ex.ErrorCode != 123) throw;
...
}
Desde C # 6.0 puedes filtrar igual que con VB:
try
{
// Do stuff
}
catch (Exception e) when (e.ErrorCode == 123456) // filter
{
// Handle, other exceptions will be left alone and bubble up
}
Depende de lo que esté haciendo en el bloque catch, y si desea pasar el error al código de llamada o no.
Podría decir Catch io.FileNotFoundExeption ex
y luego usar una ruta de archivo alternativa o algo así, pero aún así lanzar el error.
También hacer Throw
lugar de Throw Ex
te permite mantener el seguimiento de la pila completa. Throw ex reinicia el seguimiento de la pila de la declaración de lanzamiento (espero que tenga sentido).
En el ejemplo en el código que ha publicado no hay, de hecho, no tiene sentido capturar la excepción, ya que no se hace nada en la captura, solo se vuelve a mostrar, de hecho hace más daño que bien ya que la pila de llamadas se pierde. .
Sin embargo, en caso de una excepción, debería capturar una excepción para hacer algo de lógica (por ejemplo, cerrar la conexión sql del bloqueo de archivos o solo un registro) para devolver el problema al código de llamada. Esto sería más común en una capa empresarial que en el código frontal, ya que es posible que desee que el codificador que implementa su capa empresarial maneje la excepción.
Para volver a iterar, no hay ningún punto en capturar la excepción en el ejemplo que publicó. ¡NO lo hagas así!
Esto puede ser útil cuando sus funciones de programación para una biblioteca o dll.
Esta estructura de reenvío se puede usar para restablecer a propósito la pila de llamadas para que, en lugar de ver la excepción lanzada desde una función individual dentro de la función, obtenga la excepción de la misma función.
Creo que esto solo se usa para que las excepciones lanzadas sean más limpias y no entren en las "raíces" de la biblioteca.
La mayoría de las respuestas hablan del escenario catch-log-rebrow.
En lugar de escribirlo en su código, considere utilizar AOP, en particular Postsharp.Diagnostic.Toolkit with OnExceptionOptions IncludeParameterValue and IncludeThisArgument
Lo sentimos, pero muchos ejemplos como "diseño mejorado" aún huelen horriblemente o pueden ser extremadamente engañosos. Habiendo intentado {} catch {log; lanzar} es absolutamente inútil. El registro de excepciones se debe hacer en un lugar central dentro de la aplicación. De todos modos, las excepciones aumentan el seguimiento de pila, ¿por qué no registrarlas en algún lugar y cerca de los límites del sistema?
Se debe tener precaución al serializar su contexto (es decir, DTO en un ejemplo dado) solo en el mensaje de registro. Puede contener fácilmente información confidencial que uno no quiera llegar a las manos de todas las personas que pueden acceder a los archivos de registro. Y si no agrega ninguna información nueva a la excepción, realmente no veo el punto de ajuste de excepción. El buen viejo Java tiene algún punto para eso, requiere que la persona que llama sepa qué tipo de excepciones se deben esperar y luego llamar al código. Como no tiene esto en .NET, el ajuste no sirve de nada en al menos el 80% de los casos que he visto.
Mi principal razón para tener código como:
try
{
//Some code
}
catch (Exception e)
{
throw;
}
Es así que puedo tener un punto de interrupción en el retén, que tiene un objeto de excepción instanciado. Hago esto mucho durante el desarrollo / depuración. Por supuesto, el compilador me da una advertencia sobre todas las e no utilizadas, e idealmente deberían eliminarse antes de una versión de lanzamiento.
Aunque son agradables durante la depuración.
No hagas esto
try
{
...
}
catch(Exception ex)
{
throw ex;
}
Perderás la información de seguimiento de la pila ...
O bien hacer
try { ... }
catch { throw; }
O
try { ... }
catch (Exception ex)
{
throw new Exception("My Custom Error Message", ex);
}
Una de las razones por las que podría querer volver a lanzar es si está manejando diferentes excepciones, por ejemplo,
try
{
...
}
catch(SQLException sex)
{
//Do Custom Logging
//Don''t throw exception - swallow it here
}
catch(OtherException oex)
{
//Do something else
throw new WrappedException("Other Exception occured");
}
catch
{
System.Diagnostics.Debug.WriteLine("Eeep! an error, not to worry, will be handled higher up the call stack");
throw; //Chuck everything else back up the stack
}
No quieres lanzar ex - ya que esto perderá la pila de llamadas. Ver Manejo de excepciones (MSDN).
Y sí, el try ... catch no está haciendo nada útil (aparte de perder la pila de llamadas, por lo que en realidad es peor, a menos que por alguna razón no haya querido exponer esta información).
Primero; La forma en que el código en el artículo lo hace es malvado. throw ex
restablecerá la pila de llamadas en la excepción al punto donde se encuentra esta declaración de lanzamiento; perder la información sobre dónde se creó realmente la excepción.
En segundo lugar, si solo atrapas y vuelves a lanzar así, no veo ningún valor agregado, el ejemplo de código anterior sería igual de bueno (o, dado el bit de throw ex
, incluso mejor) sin el try-catch.
Sin embargo, hay casos en los que es posible que desee capturar y volver a generar una excepción. El registro podría ser uno de ellos:
try
{
// code that may throw exceptions
}
catch(Exception ex)
{
// add error logging here
throw;
}
Si bien muchas de las otras respuestas proporcionan buenos ejemplos de por qué es posible que desee capturar una excepción de reedición, nadie parece haber mencionado un escenario "final".
Un ejemplo de esto es cuando tiene un método en el que establece el cursor (por ejemplo, un cursor de espera), el método tiene varios puntos de salida (por ejemplo, si () regresa;) y desea asegurarse de que el cursor se reinicie en el Fin del método.
Para hacer esto, puedes envolver todo el código en un try / catch / finally. En el último ajuste el cursor de nuevo al cursor derecho. Para no enterrar ninguna excepción válida, vuelva a lanzarla en la trampa.
try
{
Cursor.Current = Cursors.WaitCursor;
// Test something
if (testResult) return;
// Do something else
}
catch
{
throw;
}
finally
{
Cursor.Current = Cursors.Default;
}
Un punto que las personas no han mencionado es que, si bien los lenguajes .NET no hacen una distinción adecuada, la pregunta de si uno debe tomar medidas cuando se produce una excepción y si uno la resolverá , son realmente preguntas distintas. Hay muchos casos en los que uno debe tomar medidas basadas en las excepciones que no tiene esperanza de resolver, y hay algunos casos en que todo lo que se necesita para "resolver" una excepción es desenrollar la pila hasta cierto punto, no se requieren más acciones. .
Debido a la creencia común de que solo se debe "atrapar" las cosas que se pueden "manejar", muchos códigos que deben actuar cuando se producen excepciones, no lo hacen. Por ejemplo, una gran cantidad de código adquirirá un bloqueo, colocará el objeto protegido "temporalmente" en un estado que viola sus invariantes, luego lo pondrá en un estado legítimo y luego liberará el bloqueo antes de que alguien más pueda ver el objeto. Si ocurre una excepción mientras el objeto se encuentra en un estado peligrosamente inválido, la práctica común es liberar el bloqueo con el objeto aún en ese estado. Un patrón mucho mejor sería tener una excepción que ocurra mientras el objeto está en una condición "peligrosa", invalida expresamente el bloqueo, por lo que cualquier intento futuro de adquirirlo fallará de inmediato. El uso consistente de tal patrón mejoraría en gran medida la seguridad del llamado manejo de excepciones "Pokémon", que en mi humilde opinión adquiere una mala reputación principalmente debido al código que permite que se realicen excepciones sin tomar las medidas apropiadas primero.
En la mayoría de los lenguajes .NET, la única manera de que el código tome medidas en función de una excepción es catch
(aunque sabe que no va a resolver la excepción), realizar la acción en cuestión y luego volver a throw
). Otro enfoque posible si al código no le importa qué excepción se lanza es usar un indicador ok
con un bloque try/finally
; establezca el indicador ok
en false
antes del bloqueo y en true
antes de que salga el bloque, y antes de cualquier return
que esté dentro del bloque. Luego, finally
, supongamos que si no se configura ok
, debe haber ocurrido una excepción. Tal enfoque es semánticamente mejor que un catch
/ throw
, pero es feo y es menos mantenible de lo que debería ser.
Una posible razón para atrapar y tirar es deshabilitar cualquier filtro de excepción que esté más arriba en la pila para que no se filtre ( enlace antiguo aleatorio ). Pero, por supuesto, si esa era la intención, habría un comentario allí que lo decía.
Una razón válida para volver a emitir excepciones puede ser que desee agregar información a la excepción, o tal vez incluir la excepción original en uno de sus propios creadores:
public static string SerializeDTO(DTO dto) {
try {
XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
StringWriter sWriter = new StringWriter();
xmlSer.Serialize(sWriter, dto);
return sWriter.ToString();
}
catch(Exception ex) {
string message =
String.Format("Something went wrong serializing DTO {0}", DTO);
throw new MyLibraryException(message, ex);
}
}