Estoy mirando el artículo C# - Objeto de transferencia de datos en DTO 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 parece sensato y razonable (para un novato), pero ese intento, captura y lanzamiento arroja una WtfException... ¿No es esto exactamente equivalente a no manejar excepciones en absoluto?

Por lo tanto:

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 estoy perdiendo algo fundamental sobre el manejo de errores en C#? Es más o menos lo mismo que Java (menos las excepciones marcadas), ¿no es así? ... Es decir, ambos refinaron C++.

La pregunta de desbordamiento de pila ¿ La diferencia entre volver a lanzar capturas sin parámetros y no hacer nada? parece respaldar mi afirmación de que intentar-atrapar-lanzar no es una operación.


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

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


Referencias:

respuesta

Primero; la forma en que lo hace el código en el artículo es malvada. throw exrestablecerá 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 atrapa y vuelve a lanzar así, no veo ningún valor agregado, el ejemplo de código anterior sería igual de bueno (o, dada la throw exparte, incluso mejor) sin el intento de captura.

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

no hagas esto,

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

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

O hazlo,

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

C# (antes de C# 6) no admite las "excepciones filtradas" de CIL, cosa que sí hace VB, por lo que en C# 1-5 una de las razones para volver a generar 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 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 capturar y volver a lanzar MyException si el código de error no era 123:

try 
{
   ...
}
catch(MyException ex)
{
    if (ex.ErrorCode != 123) throw;
    ...
}

Desde C# 6.0 puedes filtrar como con VB:

try 
{
  // Do stuff
} 
catch (Exception e) when (e.ErrorCode == 123456) // filter
{
  // Handle, other exceptions will be left alone and bubble up
}

Mi razón principal para tener un código como:

try
{
    //Some code
}
catch (Exception e)
{
    throw;
}

es para que pueda tener un punto de interrupción en la captura, 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 compilación de lanzamiento.

Sin embargo, son agradables durante la depuración.

Una razón válida para volver a generar excepciones puede ser que desee agregar información a la excepción, o quizás envolver la excepción original en una de su propia creación:

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

Isn't this exactly equivalent to not handling exceptions at all?

No exactamente, no es lo mismo. Restablece el seguimiento de pila 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.

No desea lanzar ex, ya que esto perderá la pila de llamadas. Consulte Manejo de excepciones (MSDN).

Y sí, try... catch no hace nada útil (aparte de perder la pila de llamadas, por lo que en realidad es peor, a menos que por alguna razón no quisiera exponer esta información).

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 función misma.

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.

Un punto que la gente no ha mencionado es que, si bien los lenguajes .NET realmente no hacen una distinción adecuada, la cuestión de si uno debe tomar medidas cuando ocurre una excepción y si la resolverá son en realidad cuestiones distintas. Hay muchos casos en los que se deben tomar medidas basadas en excepciones que no se tienen esperanzas de resolver, y hay algunos casos en los que todo lo que se necesita para "resolver" una excepción es desenredar la pila hasta cierto punto, no se requiere ninguna otra acción. .

Debido a la sabiduría común de que uno solo debe "atrapar" las cosas que puede "manejar", una gran cantidad de código que debería tomar medidas cuando ocurren excepciones, no lo hace. Por ejemplo, una gran cantidad de código adquirirá un bloqueo, pondrá el objeto protegido "temporalmente" en un estado que viola sus invariantes, luego pondrá el objeto 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" que invalide expresamente el bloqueo para que cualquier intento futuro de adquirirlo falle de inmediato.

En la mayoría de los lenguajes .NET, la única forma de que el código realice una acción en función de una excepción es catchhacerlo (aunque sepa que no va a resolver la excepción), realizar la acción en cuestión y luego volver a hacerlo throw. Otro enfoque posible si al código no le importa qué excepción se lanza es usar una okbandera con un try/finallybloque; establezca la okbandera falseantes del bloque, y trueantes de que el bloque salga, y antes de cualquiera returnque esté dentro del bloque. Luego, dentro de finally, suponga que si okno está configurado, debe haber ocurrido una excepción. Tal enfoque es semánticamente mejor que a catch/ throw, pero es feo y es menos mantenible de lo que debería ser.

Si bien muchas de las otras respuestas brindan buenos ejemplos de por qué es posible que desee capturar y volver a lanzar una excepción, nadie parece haber mencionado un escenario 'finalmente'.

Un ejemplo de esto es cuando tiene un método en el que configura el cursor (por ejemplo, en un cursor de espera), el método tiene varios puntos de salida (por ejemplo, si () regresa;) y desea asegurarse de que el cursor se restablece en el final del método.

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

try
{
    Cursor.Current = Cursors.WaitCursor;
    // Test something
    if (testResult) return;
    // Do something else
}
catch
{
    throw;
}
finally
{
     Cursor.Current = Cursors.Default;
}

Una posible razón para atrapar el lanzamiento es deshabilitar cualquier filtro de excepción que se encuentre más arriba en 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 allí que lo diga.

Depende de lo que esté haciendo en el bloque catch y de si desea pasar el error al código de llamada o no.

Puede decir Catch io.FileNotFoundExeption exy luego usar una ruta de archivo alternativa o algo similar, pero aún arrojar el error.

Además, hacer Throwen lugar de Throw Exle 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).

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

Sin embargo, detectaría una excepción para hacer algo de lógica (por ejemplo, cerrar la conexión sql del bloqueo de archivos, o simplemente algún registro) en el caso de una excepción, devolverla al código de llamada para tratarla. Esto sería más común en una capa empresarial que en el código front-end, ya que es posible que desee que el codificador que implementa su capa empresarial maneje la excepción.

Para reiterar, aunque NO tiene sentido detectar la excepción en el ejemplo que publicó. ¡NO lo hagas así!

Lo sentimos, pero muchos ejemplos como "diseño mejorado" todavía huelen horrible o pueden ser extremadamente engañosos. Habiendo intentado { } catch { log; throw } es completamente inútil. El registro de excepciones debe realizarse en un lugar central dentro de la aplicación. las excepciones aparecen en el stacktrace de todos modos, ¿por qué no registrarlas en algún lugar cerca de los límites del sistema?

Se debe tener precaución cuando serializa su contexto (es decir, DTO en un ejemplo dado) solo en el mensaje de registro. Fácilmente puede contener información confidencial que uno no quiera que llegue a 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 envolver la excepción. El buen viejo Java tiene algo de razón para eso, requiere que la persona que llama sepa qué tipo de excepciones debe esperar luego de 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.

Además de lo que han dicho los demás, vea mi respuesta a una pregunta relacionada que muestra que capturar y volver a lanzar no es una operación (está en VB, pero parte del código podría invocarse en C# desde VB).

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

En lugar de escribirlo en su código, considere usar AOP, en particular Postsharp.Diagnostic.Toolkit con OnExceptionOptions IncludeParameterValue e IncludeThisArgument

Volver a generar excepciones a través de throwes útil cuando no tiene un código particular para manejar las excepciones actuales, o en los casos en que tiene una lógica para manejar casos de error específicos pero desea omitir todos los demás.

Ejemplo:

string numberText = "";
try
{
    Console.Write("Enter an integer: ");
    numberText = Console.ReadLine();
    var result = int.Parse(numberText);

    Console.WriteLine("You entered {0}", result);
}
catch (FormatException)
{
    if (numberText.ToLowerInvariant() == "nothing")
    {
        Console.WriteLine("Please, please don't be lazy and enter a valid number next time.");
    }
    else
    {
        throw;
    }
}    
finally
{
    Console.WriteLine("Freed some resources.");
}
Console.ReadKey();

Sin embargo, también hay otra forma de hacerlo, usando cláusulas condicionales en bloques catch:

string numberText = "";
try
{
    Console.Write("Enter an integer: ");
    numberText = Console.ReadLine();
    var result = int.Parse(numberText);

    Console.WriteLine("You entered {0}", result);
}
catch (FormatException) when (numberText.ToLowerInvariant() == "nothing")
{
    Console.WriteLine("Please, please don't be lazy and enter a valid number next time.");
}    
finally
{
    Console.WriteLine("Freed some resources.");
}
Console.ReadKey();

This mechanism is more efficient than re-throwing an exception because of the .NET runtime doesn’t have to rebuild the exception object before re-throwing it.