exception-handling tipos - ¿Por qué atrapar y volver a lanzar una excepción en C#?




programacion el (14)

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

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:


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.


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

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.


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


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
}

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í!


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


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


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.


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.


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
}

Al comparar los artículos de MSDN "Novedades en el lenguaje y compilador de C # 2.0" y " Novedades en Visual C # 2005 ", es posible deducir que "C # major_version.minor_version" está acuñado de acuerdo con la numeración de la versión del compilador.

Hay un C # 1.2 correspondiente a .NET 1.1 y VS 2003 y también denominado Visual C # .NET 2003 .

Pero más adelante, Microsoft se detuvo para incrementar los números de la versión secundaria (después del punto) o para que no fueran cero, 0 . Aunque se debe tener en cuenta que C # correspondiente a .NET 3.5 se nombra en msdn.microsoft.com como "Visual C # 2008 Service Pack 1" .

Hay dos nombres paralelos: por .NET principal / numeración de versión del compilador y por numeración de Visual Studio.

C # 2.0 es un sinónimo de Visual C # 2005

C # 3.0 corresponde (o, más correctamente, puede apuntar) a:





c# exception-handling try-catch