¿Cuál es la forma correcta de volver a lanzar una excepción en C #?

Tengo una pregunta para ti que surge de mi compañero haciendo las cosas de una manera diferente a como lo hago yo.

¿Es mejor hacer esto?

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

o esto:

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

¿Hacen lo mismo? ¿Es uno mejor que el otro?

Siempre debe usar la siguiente syntax para volver a lanzar una excepción, de lo contrario pisará el rastro de la stack:

 throw; 

Si imprime la traza resultante de “throw ex”, verá que termina en esa statement y no en la fuente real de la excepción.

Básicamente, se debe considerar una ofensa criminal usar “throw ex”.

Mis preferencias es usar

 try { } catch (Exception ex) { ... throw new Exception ("Put more context here", ex) } 

Esto conserva el error original, pero le permite poner más contexto, como una identificación de objeto, una cadena de conexión, cosas así. A menudo, mi herramienta de informes de excepción tendrá 5 excepciones encadenadas para informar, cada una informando con más detalle.

Si lanza una excepción sin una variable (el segundo ejemplo), StackTrace incluirá el método original que arrojó la excepción.

En el primer ejemplo, StackTrace se cambiará para reflejar el método actual.

Ejemplo:

 static string ReadAFile(string fileName) { string result = string.Empty; try { result = File.ReadAllLines(fileName); } catch(Exception ex) { throw ex; // This will show ReadAFile in the StackTrace throw; // This will show ReadAllLines in the StackTrace } 

El primero conserva el rastro de stack original de la excepción, el segundo lo reemplaza con la ubicación actual.

Por lo tanto, el primero es de lejos el mejor.

Sé que esta es una vieja pregunta, pero la voy a responder porque no estoy de acuerdo con todas las respuestas aquí.

Ahora, estaré de acuerdo en que la mayoría de las veces desea hacer un throw simple, preservar la mayor cantidad de información posible sobre lo que salió mal, o si desea lanzar una nueva excepción que pueda contener eso como una excepción interna, o no, dependiendo de la probabilidad de que desee saber sobre los eventos internos que lo causaron.

Sin embargo, hay una excepción. Hay varios casos en que un método llamará a otro método y una condición que causa una excepción en la llamada interna se debe considerar la misma excepción en la llamada externa.

Un ejemplo es una colección especializada implementada mediante el uso de otra colección. Digamos que es una DistinctList que envuelve una List pero rechaza los elementos duplicados.

Si alguien llamó a ICollection.CopyTo en su clase de colección, podría ser simplemente una llamada directa a CopyTo en la colección interna (si, por ejemplo, toda la lógica personalizada solo se aplica para agregar a la colección o configurarla). Ahora, las condiciones en las que lanzaría esa llamada son exactamente las mismas condiciones en las que debería lanzar su colección para coincidir con la documentación de ICollection.CopyTo .

Ahora, simplemente no puedes ver la excepción y dejarla pasar. Aquí, sin embargo, el usuario obtiene una excepción de List cuando llamaban a algo en DistinctList . No es el fin del mundo, pero es posible que desee ocultar los detalles de implementación.

O puede hacer su propia comprobación:

 public CopyTo(T[] array, int arrayIndex) { if(array == null) throw new ArgumentNullException("array"); if(arrayIndex < 0) throw new ArgumentOutOfRangeException("arrayIndex", "Array Index must be zero or greater."); if(Count > array.Length + arrayIndex) throw new ArgumentException("Not enough room in array to copy elements starting at index given."); _innerList.CopyTo(array, arrayIndex); } 

Ese no es el peor código porque es un estándar y probablemente podamos simplemente copiarlo de alguna otra implementación de CopyTo en la que no fue un simple paso y tuvimos que implementarlo nosotros mismos. Aún así, es innecesariamente repetir exactamente las mismas comprobaciones que se van a hacer en _innerList.CopyTo(array, arrayIndex) , por lo que lo único que se ha agregado a nuestro código es 6 líneas donde podría haber un error.

Podríamos verificar y envolver:

 public CopyTo(T[] array, int arrayIndex) { try { _innerList.CopyTo(array, arrayIndex); } catch(ArgumentNullException ane) { throw new ArgumentNullException("array", ane); } catch(ArgumentOutOfRangeException aore) { throw new ArgumentOutOfRangeException("Array Index must be zero or greater.", aore); } catch(ArgumentException ae) { throw new ArgumentException("Not enough room in array to copy elements starting at index given.", ae); } } 

En términos de nuevo código agregado que podría ser defectuoso, esto es aún peor. Y no ganamos nada de las excepciones internas. Si pasamos una matriz nula a este método y recibimos una ArgumentNullException , no aprenderemos nada examinando la excepción interna y _innerList.CopyTo que una llamada a _innerList.CopyTo pasó una matriz nula y arrojó una ArgumentNullException .

Aquí, podemos hacer todo lo que queramos con:

 public CopyTo(T[] array, int arrayIndex) { try { _innerList.CopyTo(array, arrayIndex); } catch(ArgumentException ae) { throw ae; } } 

Todas las excepciones que esperamos tener que lanzar si el usuario lo llama con argumentos incorrectos, serán lanzadas correctamente por ese relanzamiento. Si hay un error en la lógica utilizada aquí, está en una de dos líneas: o nos equivocamos al decidir que este era un caso en el que funcionaba este enfoque, o nos equivocamos al tener a ArgumentException como el tipo de excepción buscado. Son los únicos dos errores que el bloque catch puede tener.

Ahora. Aún así estoy de acuerdo en que la mayoría de las veces quieres un throw; simple throw; o si desea construir su propia excepción para hacer coincidir más directamente el problema desde la perspectiva del método en cuestión. Hay casos como el anterior en los que relanzarlos tiene más sentido y hay muchos otros casos. Por ejemplo, para tomar un ejemplo muy diferente, si un lector de archivos ATOM implementado con un FileStream y un XmlTextReader recibe un error de archivo o un XML no válido, tal vez quiera arrojar exactamente la misma excepción que recibió de esas clases, pero debería tener el aspecto de la persona que llama que es AtomFileReader que está lanzando una FileNotFoundException o XmlException , por lo que podrían ser candidatos para volver a lanzar de forma similar.

Editar:

También podemos combinar los dos:

 public CopyTo(T[] array, int arrayIndex) { try { _innerList.CopyTo(array, arrayIndex); } catch(ArgumentException ae) { throw ae; } catch(Exception ex) { //we weren't expecting this, there must be a bug in our code that put //us into an invalid state, and subsequently let this exception happen. LogException(ex); throw; } } 

Siempre debes usar “lanzar”; para volver a lanzar las excepciones en .NET,

Referir esto, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

Básicamente MSIL (CIL) tiene dos instrucciones: “throw” y “rethrow” y “throw ex” de C #. se comstack en “throw” de MSIL y “throw” de C #; – ¡en “re-lanzamiento” de MSIL! Básicamente, puedo ver el motivo por el cual “throw ex” anula el seguimiento de la stack.

El primero es mejor. Si intenta depurar el segundo y mira la stack de llamadas, no verá de dónde vino la excepción original. Hay trucos para mantener la stack de llamadas intacta (prueba la búsqueda, se ha respondido antes) si realmente necesitas volver a lanzar.

Descubrí que si la excepción se lanza con el mismo método que está capturada, el seguimiento de la stack no se conserva, por lo que vale.

 void testExceptionHandling() { try { throw new ArithmeticException("illegal expression"); } catch (Exception ex) { throw; } finally { System.Diagnostics.Debug.WriteLine("finally called."); } } 

Depende. En una comstackción de depuración, quiero ver el trazado original de la stack con el mínimo esfuerzo posible. En ese caso, “lanzar”; encaja a la perfección. Sin embargo, en una comstackción de lanzamiento, (a) deseo registrar el error con el seguimiento de la stack original incluido, y una vez hecho esto, (b) remodelar el manejo del error para que tenga más sentido para el usuario. Aquí “Throw Exception” tiene sentido. Es cierto que volver a tirar el error descarta la traza original de la stack, pero un no desarrollador no obtiene nada al ver la información de seguimiento de la stack, por lo que está bien volver a generar el error.

  void TrySuspectMethod() { try { SuspectMethod(); } #if DEBUG catch { //Don't log error, let developer see //original stack trace easily throw; #else catch (Exception ex) { //Log error for developers and then //throw a error with a user-oriented message throw new Exception(String.Format ("Dear user, sorry but: {0}", ex.Message)); #endif } } 

La forma en que está redactada la pregunta, enfrentando “Lanzar:” vs. “Lanzar ex”; lo hace un poco desilusionante. La verdadera elección es entre “lanzar”; y “Throw Exception”, donde “Throw ex”; es un caso especial improbable de “Lanzar excepción”.