¿Por qué capturar y repensar una excepción en C#?


Estoy mirando el artículo C# - Objeto de Transferencia de Datos en DTOS serializables.

El artículo incluye este fragmento 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 se ve sano y razonable (para un novato), pero ese intento-catch-throw lanza una excepción WtfException... ¿No es esto exactamente equivalente a no manejar excepciones en absoluto?

Ergo:

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 más o menos lo mismo que Java (menos las excepciones marcadas), ¿no? ... 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 afirmación de que try-catch-throw es-un no-op.


EDITAR:

Solo para resumir para cualquiera que encuentre este hilo en el futuro...

NO

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 ¡causa raíz del problema!

DO

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

Captura las excepciones más específicas antes que las menos específicas (al igual que Java).


Referencias:

Author: Community, 2009-05-19

16 answers

Primero; la forma en que el código en el artículo lo hace es mala. throw ex restablecerá la pila de llamadas en la excepción al punto donde está esta instrucción throw; perdiendo la información sobre dónde se creó realmente la excepción.

En segundo lugar, si solo atrapa y vuelve a lanzar de esa manera, no veo ningún valor añadido, el ejemplo de código anterior sería igual de bueno (o, dado el bit throw ex, incluso mejor) sin el try-catch.

Sin embargo, hay casos en los que es posible que desee atrapar y repensar 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;
}
 361
Author: Fredrik Mörk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-19 08:06:23

No hagas esto,

try 
{
...
}
catch(Exception ex)
{
   throw ex;
}

Perderá la información de seguimiento de la pila...

O bien,

try { ... }
catch { throw; }

O

try { ... }
catch (Exception ex)
{
    throw new Exception("My Custom Error Message", ex);
}

Una de las razones por las que es posible que desee repensar es si está manejando diferentes excepciones, para 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
}
 98
Author: Eoin Campbell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-13 14:13:31

C# (antes de C# 6) no soporta CIL "excepciones filtradas", 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 catch() para determinar si realmente quería capturar la excepción.

Por ejemplo, en VB se puede 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 capturar y volver a lanzar la 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
}
 48
Author: bzlm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-18 03:55:17

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 catch, que tiene un objeto de excepción instanciado. Hago esto mucho mientras desarrollo / depuro. Por supuesto, el compilador me da una advertencia sobre todas las e no utilizadas, e idealmente deberían eliminarse antes de una compilación de liberación.

Sin embargo, son agradables durante la depuración.

 12
Author: Peter Mortensen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-09 14:28:42

Una razón válida para repensar excepciones puede ser que desee agregar información a la excepción, o tal vez envolver la excepción original en una de sus propias creaciones:

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);
  }
}
 10
Author: edosoft,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-19 08:45:19

¿No es esto exactamente equivalente a no ¿manejar excepciones?

No exactamente, no es lo mismo. Restablece el stacktrace de la excepción. Aunque estoy de acuerdo en que esto probablemente es un error, y por lo tanto un ejemplo de código malo.

 9
Author: Arjan Einbu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-19 08:11:11

No desea lanzar ex - ya que esto perderá la pila de llamadas. Véase Gestión de Excepciones (MSDN).

Y sí, el intento...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 quieras exponer esta información).

 7
Author: Duncan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-09 14:26:09

Un punto que la gente no ha mencionado es que mientras que los lenguajes.NET realmente no hacen una distinción adecuada, la cuestión de si uno debe tomar acción cuando ocurre una excepción, y si uno resolver, son en realidad preguntas distintas. Hay muchos casos en los que uno debe tomar medidas basadas en excepciones que no tiene esperanza de resolver, y hay algunos casos en los que todo lo que es necesario para "resolver" una excepción es desenrollar la pila a un cierto punto no no se requiere ninguna otra acción.

Debido a la sabiduría común de que uno solo debería "atrapar" cosas que uno puede "manejar", una gran cantidad de código que debe tomar acción cuando ocurren excepciones, no lo hace. Por ejemplo, una gran cantidad de código adquirirá un bloqueo, pondrá el objeto guardado "temporalmente" en un estado que viole 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 se produce una excepción mientras el objeto está estado peligrosamente inválido, la práctica común es liberar el bloqueo con el objeto todavía en ese estado. Un patrón mucho mejor sería tener una excepción que ocurre mientras el objeto está en una condición" peligrosa " invalida expresamente el bloqueo para que cualquier intento futuro de adquirirlo falle inmediatamente. El uso consistente de tal patrón mejoraría en gran medida la seguridad del llamado manejo de excepciones" Pokemon", que en mi humilde opinión obtiene una mala reputación principalmente debido al código que permite excepciones para filtrarse sin tomar las medidas apropiadas primero.

En la mayoría de los lenguajes.NET, la única manera de que el código tome acción basada en una excepción es catch (aunque sepa 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 una bandera ok con un bloque try/finally; establezca la bandera ok en false antes del bloque, y en true antes de que el bloque salga, y antes de cualquier return que esté dentro del bloque. Entonces, dentro de finally, asuma que si ok no está establecido, 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.

 5
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-14 20:16:51

Una posible razón para atrapar-lanzar es deshabilitar cualquier filtro de excepción más profundo de la pila para que no se filtre hacia abajo ( enlace antiguo aleatorio). Pero, por supuesto, si esa fuera la intención, habría un comentario que lo dijera.

 3
Author: Brian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-30 15:12:20

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 en lugar de Throw Ex le permite mantener el seguimiento completo de la pila. Throw ex reinicia el seguimiento de la pila desde la instrucción throw (espero que tenga sentido).

 3
Author: Pondidum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-09 14:27:36

Mientras que muchas de las otras respuestas proporcionan buenos ejemplos de por qué es posible que desee tomar un rethrow como una excepción, nadie parece haber mencionado un escenario 'finalmente'.

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, if () return;) y desea asegurarse de que el cursor se restablezca al final del método.

Para hacer esto puede envolver todo el código en un try/catch/finally. En el finalmente vuelva a colocar el cursor en el cursor derecho. Para que no entierres ninguna excepción válida, repensala en la captura.

try
{
    Cursor.Current = Cursors.WaitCursor;
    // Test something
    if (testResult) return;
    // Do something else
}
catch
{
    throw;
}
finally
{
     Cursor.Current = Cursors.Default;
}
 3
Author: statler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-09 11:47:08

Esto puede ser útil cuando su programación funciona para una biblioteca o dll.

Esta estructura de rethrow se puede usar para restablecer intencionadamente la pila de llamadas de modo 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 propia 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.

 3
Author: Jackson Tarisa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-27 15:35:30

En el ejemplo en el código que ha publicado, de hecho, no tiene sentido capturar la excepción, ya que no hay nada hecho en la captura, solo se vuelve a usar, de hecho, hace más daño que bien, ya que la pila de llamadas se pierde.

Usted, sin embargo, coger una excepción para hacer alguna lógica (por ejemplo, el cierre de la conexión sql de bloqueo de archivos, o simplemente algunos registros) en el caso de una excepción, el tiro de nuevo al código de llamada para tratar. Esto sería más común en una capa de negocio que código front end como puede querer que el codificador implemente su capa de negocio para manejar la excepción.

Para volver a iterar aunque NO tiene sentido capturar la excepción en el ejemplo que publicaste. ¡NO lo hagas así!

 2
Author: Sheff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-19 08:19:11

Lo siento, pero muchos ejemplos como "diseño mejorado" todavía huelen horriblemente o pueden ser extremadamente engañosos. Tener try {} catch { log ; throw} es completamente inútil. El registro de excepciones debe hacerse en un lugar central dentro de la aplicación. de todos modos, ¿por qué no registrarlos en algún lugar arriba y cerca de los bordes del sistema?

Se debe tener precaución cuando se serializa el contexto (es decir, DTO en un ejemplo dado) solo en el mensaje de registro. Puede fácilmente contener información sensible uno podría no querer llegar a las manos de todas las personas que pueden acceder a los archivos de registro. Y si no agregas ninguna información nueva a la excepción, realmente no veo el punto de envoltura de la excepción. El buen viejo Java tiene algún punto para eso, requiere que el llamante sepa qué tipo de excepciones se deben esperar y luego llamar al código. Dado que no tiene esto en.NET, envolver no hace ningún bien en al menos el 80% de los casos que he visto.

 2
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-16 07:22:00

Además de lo que los otros han dicho, ver mi respuesta a una pregunta relacionada que muestra que catching and rethrowing no es un no-op (está en VB, pero parte del código podría ser invocado en C# desde VB).

 1
Author: erikkallen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:38

La mayoría de las respuestas hablan del escenario catch-log-rethrow.

En lugar de escribirlo en su código considere usar AOP, en particular Postsharp.Diagnostico.Toolkit con OnExceptionOptions include Parameterervalue and Incluye este argumento

 1
Author: Michael Freidgeim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-25 22:41:10