try tipos propiedades propagacion practicas manejo las excepciones ejemplos crear catch capas buenas c# .net exception exception-handling

c# - tipos - ¿Atrapa múltiples excepciones a la vez?



tipos de excepciones en c# (26)

Se desaconseja simplemente atrapar System.Exception . En su lugar, solo se deben capturar las excepciones "conocidas".

Ahora, esto a veces conduce a un código repetitivo innecesario, por ejemplo:

try { WebId = new Guid(queryString["web"]); } catch (FormatException) { WebId = Guid.Empty; } catch (OverflowException) { WebId = Guid.Empty; }

Me pregunto: ¿hay una manera de detectar ambas excepciones y solo llamar a la llamada WebId = Guid.Empty una vez?

El ejemplo dado es bastante simple, ya que es solo un GUID . Pero imagine el código en el que modifica un objeto varias veces, y si una de las manipulaciones falla de la manera esperada, desea "reiniciar" el object . Sin embargo, si hay una excepción inesperada, todavía quiero lanzar eso más alto.


Precaución y advertencia: otro estilo funcional y amable.

Lo que se encuentra en el enlace no responde su pregunta directamente, pero es trivial extenderla para que parezca:

static void Main() { Action body = () => { ...your code... }; body.Catch<InvalidOperationException>() .Catch<BadCodeException>() .Catch<AnotherException>(ex => { ...handler... })(); }

(Básicamente, proporciona otra sobrecarga de Catch vacía que se devuelve a sí misma)

La pregunta más grande a esto es por qué . No creo que el costo supere la ganancia aquí :)


@Micheal

Versión ligeramente revisada de su código:

catch (Exception ex) { Type exType = ex.GetType(); if (exType == typeof(System.FormatException) || exType == typeof(System.OverflowException) { WebId = Guid.Empty; } else { throw; } }

Las comparaciones de cadenas son feas y lentas.


Coger System.Exception y encender los tipos

catch (Exception ex) { if (ex is FormatException || ex is OverflowException) { WebId = Guid.Empty; return; } throw; }


Como han señalado otros, puede tener una instrucción if dentro de su bloque catch para determinar qué está sucediendo. C # 6 admite filtros de excepción, por lo que funcionará lo siguiente:

try { … } catch (Exception e) when (MyFilter(e)) { … }

El método MyFilter podría verse así:

private bool MyFilter(Exception e) { return e is ArgumentNullException || e is FormatException; }

Alternativamente, todo esto se puede hacer en línea (el lado derecho de la instrucción when solo tiene que ser una expresión booleana).

try { … } catch (Exception e) when (e is ArgumentNullException || e is FormatException) { … }

Esto es diferente de usar una instrucción if desde dentro del bloque catch , el uso de filtros de excepción no desenrollará la pila.

Puede descargar Visual Studio 2015 para verificar esto.

Si desea continuar utilizando Visual Studio 2013, puede instalar el siguiente paquete nuget:

Install-Package Microsoft.Net.Compilers

Al momento de escribir, esto incluirá soporte para C # 6.

La referencia a este paquete hará que el proyecto se genere utilizando la versión específica de los compiladores de C # y Visual Basic contenidos en el paquete, a diferencia de cualquier versión instalada del sistema.


Con C # 7, la respuesta de Michael Stum se puede mejorar al tiempo que se mantiene la legibilidad de una declaración de cambio:

catch (Exception ex) { switch (ex) { case FormatException _: case OverflowException _: WebId = Guid.Empty; break; default: throw; } }


Desafortunadamente, no en C #, ya que necesitaría un filtro de excepción para hacerlo y C # no expone esa característica de MSIL. VB.NET tiene esta capacidad, por ejemplo,

Catch ex As Exception When TypeOf ex Is FormatException OrElse TypeOf ex Is OverflowException

Lo que podría hacer es usar una función anónima para encapsular su código de error y luego invocarlo en esos bloques de captura específicos:

Action onError = () => WebId = Guid.Empty; try { // something } catch (FormatException) { onError(); } catch (OverflowException) { onError(); }


En aras de la exhaustividad, desde .NET 4.0 el código puede reescribirse como:

Guid.TryParse(queryString["web"], out WebId);

TryParse nunca lanza excepciones y devuelve falso si el formato es incorrecto, configurando WebId en Guid.Empty .

Desde C # 7 puedes evitar introducir una variable en una línea separada:

Guid.TryParse(queryString["web"], out Guid webId);

También puede crear métodos para analizar tuplas de retorno, que aún no están disponibles en .NET Framework a partir de la versión 4.6:

(bool success, Guid result) TryParseGuid(string input) => (Guid.TryParse(input, out Guid result), result);

Y úsalos así:

WebId = TryParseGuid(queryString["web"]).result; // or var tuple = TryParseGuid(queryString["web"]); WebId = tuple.success ? tuple.result : DefaultWebId;

La próxima actualización inútil de esta respuesta inútil se produce cuando la deconstrucción de los parámetros de salida se implementa en C # 12. :)


Esta es una variante de la respuesta de Matt (creo que esto es un poco más limpio) ... use un método:

public void TryCatch(...) { try { // something return; } catch (FormatException) {} catch (OverflowException) {} WebId = Guid.Empty; }

Se lanzará cualquier otra excepción y el código WebId = Guid.Empty; no será golpeado Si no desea que otras excepciones bloqueen su programa, simplemente agregue esto DESPUÉS de las otras dos capturas:

... catch (Exception) { // something, if anything return; // only need this if you follow the example I gave and put it all in a method }


La respuesta aceptada parece aceptable, excepto que CodeAnalysis / FxCop se quejará del hecho de que está detectando un tipo de excepción general.

Además, parece que el operador "es" podría degradar el rendimiento ligeramente.

CA1800: No emitir innecesariamente dice que "considere probar el resultado del operador ''como'' en su lugar", pero si lo hace, estará escribiendo más código que si detecta cada excepción por separado.

De todos modos, esto es lo que yo haría:

bool exThrown = false; try { // Something } catch (FormatException) { exThrown = true; } catch (OverflowException) { exThrown = true; } if (exThrown) { // Something else }


Los filtros de excepción ahora están disponibles en c # 6+. Tu puedes hacer

try { WebId = new Guid(queryString["web"]); } catch (Exception ex) when(ex is FormatException || ex is OverflowException) { WebId = Guid.Empty; }


Qué tal si

try { WebId = Guid.Empty; WebId = new Guid(queryString["web"]); } catch (FormatException) { } catch (OverflowException) { }


Si no desea usar una instrucción if dentro de los ámbitos de catch , en C# 6.0 puede usar la sintaxis de los Exception Filters que ya era compatible con el CLR en las versiones previas pero que solo existía en VB.NET / MSIL :

try { WebId = new Guid(queryString["web"]); } catch (Exception exception) when (exception is FormatException || ex is OverflowException) { WebId = Guid.Empty; }

Este código detectará la Exception solo cuando se InvalidDataException una Exception InvalidDataException o ArgumentNullException .

En realidad, puedes poner básicamente cualquier condición dentro de esa cláusula when :

static int a = 8; ... catch (Exception exception) when (exception is InvalidDataException && a == 8) { Console.WriteLine("Catch"); }

Tenga en cuenta que, a diferencia de una instrucción if dentro del catch del catch , los Exception Filters no pueden lanzar Exceptions , y cuando lo hacen, o cuando la condición no es true , se evaluará la siguiente condición de catch :

static int a = 7; static int b = 0; ... try { throw new InvalidDataException(); } catch (Exception exception) when (exception is InvalidDataException && a / b == 2) { Console.WriteLine("Catch"); } catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException) { Console.WriteLine("General catch"); }

Salida: captura general.

Cuando haya más de un true Exception Filter , se aceptará el primero:

static int a = 8; static int b = 4; ... try { throw new InvalidDataException(); } catch (Exception exception) when (exception is InvalidDataException && a / b == 2) { Console.WriteLine("Catch"); } catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException) { Console.WriteLine("General catch"); }

Salida: captura.

Y como puede ver en la MSIL el código no se traduce a las declaraciones if , sino a los Filters , y las Exceptions no se pueden lanzar desde las áreas marcadas con el Filter 1 y el Filter 2 pero el filtro que lanza la Exception fallará en su lugar, también la última el valor de comparación endfilter en la pila antes de que el comando de endfilter final determine el éxito / fracaso del filtro (la Catch 1 XOR Catch 2 se ejecutará en consecuencia):

Además, específicamente Guid tiene el método Guid.TryParse .


Si puede actualizar su aplicación a C # 6, tiene suerte. La nueva versión de C # ha implementado filtros de excepción. Así que puedes escribir esto:

catch (Exception ex) when (ex is FormatException || ex is OverflowException) { WebId = Guid.Empty; }

Algunas personas piensan que este código es el mismo que

catch (Exception ex) { if (ex is FormatException || ex is OverflowException) { WebId = Guid.Empty; } throw; }

Pero no lo es. En realidad, esta es la única característica nueva en C # 6 que no es posible emular en versiones anteriores. En primer lugar, un relanzamiento significa más sobrecarga que saltar la captura. Segundo, no es semánticamente equivalente. La nueva característica conserva la pila intacta cuando está depurando su código. Sin esta característica, el volcado de emergencia es menos útil o incluso inútil.

Vea una roslyn.codeplex.com/discussions/541301 . Y un ejemplo que muestra la diferencia .


en C # 6, el enfoque recomendado es utilizar filtros de excepción, aquí hay un ejemplo:

try { throw new OverflowException(); } catch(Exception e ) when ((e is DivideByZeroException) || (e is OverflowException)) { // this will execute iff e is DividedByZeroEx or OverflowEx Console.WriteLine("E"); }


¿Quizás intente mantener su código simple, como poner el código común en un método, como lo haría en cualquier otra parte del código que no esté dentro de una cláusula catch?

P.ej:

try { // ... } catch (FormatException) { DoSomething(); } catch (OverflowException) { DoSomething(); } // ... private void DoSomething() { // ... }

Justo como lo haría, tratando de encontrar el patrón simple es hermoso


La respuesta de Joseph Daigle es una buena solución, pero encontré que la siguiente estructura es un poco más ordenada y menos propensa a errores.

catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw; // Handle exception }

Hay algunas ventajas de invertir la expresión:

  • No es necesaria una declaración de devolución.
  • El código no está anidado
  • No hay riesgo de olvidar las declaraciones de ''lanzar'' o ''devolver'' que en la solución de Joseph están separadas de la expresión.

Incluso se puede compactar en una sola línea (aunque no es muy bonita)

catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw; // Handle exception }

Edición: el filtro de excepciones en C # 6.0 hará que la sintaxis sea un poco más limpia y ofrece una serie de otros beneficios sobre cualquier solución actual. (notablemente dejando la pila ilesa)

Aquí es cómo se vería el mismo problema usando la sintaxis de C # 6.0:

catch(Exception ex) when (ex is SomeException || ex is OtherException) { // Handle exception }


EDITAR: Estoy de acuerdo con otros que dicen que, a partir de C # 6.0, los filtros de excepción ahora son una manera perfecta de ir: catch (Exception ex) when (ex is ... || ex is ... )

Excepto que aún odio el diseño de una línea larga y personalmente pondré el código como el siguiente. Creo que esto es tan funcional como estético, ya que creo que mejora la comprensión. Algunos pueden estar en desacuerdo:

catch (Exception ex) when ( ex is ... || ex is ... || ex is ... )

ORIGINAL:

Sé que llego un poco tarde a la fiesta aquí, pero santo humo ...

Directamente a la persecución, este tipo de duplicados de una respuesta anterior, pero si realmente desea realizar una acción común para varios tipos de excepción y mantener todo limpio y ordenado dentro del alcance del método, ¿por qué no usar un lambda? / cierre / función en línea para hacer algo como lo siguiente? Quiero decir, es muy probable que termines dándote cuenta de que solo quieres hacer de ese cierre un método separado que puedas utilizar en cualquier lugar. Pero luego será muy fácil hacerlo sin cambiar realmente estructuralmente el resto del código. ¿Derecha?

private void TestMethod () { Action<Exception> errorHandler = ( ex ) => { // write to a log, whatever... }; try { // try some stuff } catch ( FormatException ex ) { errorHandler ( ex ); } catch ( OverflowException ex ) { errorHandler ( ex ); } catch ( ArgumentNullException ex ) { errorHandler ( ex ); } }

No puedo evitar preguntarme ( advertencia: un poco de ironía / sarcasmo por delante) por qué en la tierra se esfuerza para reemplazar básicamente lo siguiente:

try { // try some stuff } catch( FormatException ex ){} catch( OverflowException ex ){} catch( ArgumentNullException ex ){}

... con alguna variación loca de este próximo olor de código, me refiero a ejemplo, solo para pretender que está guardando algunas pulsaciones de teclas.

// sorta sucks, let''s be honest... try { // try some stuff } catch( Exception ex ) { if (ex is FormatException || ex is OverflowException || ex is ArgumentNullException) { // write to a log, whatever... return; } throw; }

Porque ciertamente no es automáticamente más legible.

Concedido, dejé las tres instancias idénticas de /* write to a log, whatever... */ return; Fuera del primer ejemplo.

Pero ese es mi punto de vista. Todos ustedes han oído hablar de funciones / métodos, ¿verdad? Seriamente. Escriba una función de ErrorHandler común y, como, ErrorHandler desde cada bloque catch.

Si me pregunta, el segundo ejemplo (con las palabras clave if y is ) es mucho menos legible y, al mismo tiempo, mucho más propenso a errores durante la fase de mantenimiento de su proyecto.

La fase de mantenimiento, para cualquier persona que pueda ser relativamente nueva en la programación, comprenderá el 98.7% o más de la vida útil general de su proyecto, y la mala persona que realiza el mantenimiento es casi seguro que será otra persona que no sea usted. Y hay una gran probabilidad de que pasen el 50% de su tiempo en el trabajo maldiciendo tu nombre.

Y, por supuesto, FxCop te ladra y, por lo tanto, también debes agregar un atributo a tu código que tenga un código zip que tenga que ver con el programa en ejecución, y solo está ahí para decirle a FxCop que ignore un problema que en el 99.9% de los casos es totalmente correcto en el marcado. Y, lo siento, podría estar equivocado, pero ¿ese atributo "ignorar" no se compila en tu aplicación?

¿Poner todo el test de if en una línea lo haría más legible? No lo creo. Quiero decir, hace tiempo que otro programador argumentó con vehemencia que poner más código en una línea lo haría "correr más rápido". Pero, por supuesto, estaba completamente loco. Tratando de explicarle (con una cara seria, lo que fue un desafío) cómo el intérprete o el compilador dividirían esa larga línea en declaraciones discretas de una instrucción por línea, esencialmente idénticas al resultado si hubiera seguido adelante y acaba de hacer que el código sea legible en lugar de tratar de superar al compilador, no tuvo ningún efecto en él en absoluto. Pero yo divago.

¿Cuánto menos legible obtiene esto cuando agrega tres tipos de excepciones más, uno o dos meses a partir de ahora? (Respuesta: se vuelve mucho menos legible).

Uno de los puntos principales, en realidad, es que la mayor parte del formato del código fuente textual que todos estamos viendo todos los días es hacer que sea realmente obvio para otros seres humanos lo que realmente sucede cuando se ejecuta el código. Debido a que el compilador convierte el código fuente en algo totalmente diferente y no podría importarle menos su estilo de formato de código. Así que todo en una línea apesta totalmente, también.

Solo digo...

// super sucks... catch( Exception ex ) { if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException ) { // write to a log, whatever... return; } throw; }


Como sentí que estas respuestas solo tocaban la superficie, intenté profundizar un poco más.

Entonces, lo que realmente queremos hacer es algo que no se compila, por ejemplo:

// Won''t compile... damn public static void Main() { try { throw new ArgumentOutOfRangeException(); } catch (ArgumentOutOfRangeException) catch (IndexOutOfRangeException) { // ... handle }

La razón por la que queremos esto es porque no queremos que el controlador de excepciones detecte las cosas que necesitamos más adelante en el proceso. Claro, podemos atrapar una Excepción y verificar con un ''si'' qué hacer, pero seamos sinceros, realmente no queremos eso. (FxCop, problemas de depuración, fealdad)

Entonces, ¿por qué este código no se compila, y cómo podemos hackearlo de tal manera que lo haga?

Si miramos el código, lo que realmente nos gustaría hacer es reenviar la llamada. Sin embargo, de acuerdo con MS Partition II, los bloques de manejadores de excepciones de IL no funcionarán de esta manera, lo que en este caso tiene sentido porque implicaría que el objeto de "excepción" puede tener diferentes tipos.

O para escribirlo en código, le pedimos al compilador que haga algo como esto (bueno, no es del todo correcto, pero supongo que es lo más cercano posible):

// Won''t compile... damn try { throw new ArgumentOutOfRangeException(); } catch (ArgumentOutOfRangeException e) { goto theOtherHandler; } catch (IndexOutOfRangeException e) { theOtherHandler: Console.WriteLine("Handle!"); }

La razón por la que esto no se compilará es bastante obvia: ¿qué tipo y valor tendría el objeto ''$ excepción'' (que están almacenados aquí en las variables ''e'')? La forma en que queremos que el compilador maneje esto es tener en cuenta que el tipo base común de ambas excepciones es ''Excepción'', usar eso para que una variable contenga ambas excepciones y luego manejar solo las dos excepciones que se detectan. La forma en que esto se implementa en IL es como ''filtro'', que está disponible en VB.Net.

Para que funcione en C #, necesitamos una variable temporal con el tipo base correcto de ''Excepción''. Para controlar el flujo del código, podemos agregar algunas ramas. Aquí va:

Exception ex; try { throw new ArgumentException(); // for demo purposes; won''t be caught. goto noCatch; } catch (ArgumentOutOfRangeException e) { ex = e; } catch (IndexOutOfRangeException e) { ex = e; } Console.WriteLine("Handle the exception ''ex'' here :-)"); // throw ex ? noCatch: Console.WriteLine("We''re done with the exception handling.");

Las desventajas obvias para esto son que no podemos volver a lanzar correctamente y, bueno, seamos honestos, que es una solución bastante fea. La fealdad se puede arreglar un poco al realizar la eliminación de ramas, lo que hace que la solución sea un poco mejor:

Exception ex = null; try { throw new ArgumentException(); } catch (ArgumentOutOfRangeException e) { ex = e; } catch (IndexOutOfRangeException e) { ex = e; } if (ex != null) { Console.WriteLine("Handle the exception here :-)"); }

Eso deja solo el ''re-lanzamiento''. Para que esto funcione, debemos poder realizar el manejo dentro del bloque ''catch'', y la única forma de hacer que esto funcione es mediante un objeto ''Exception'' que captura.

En este punto, podemos agregar una función separada que maneje los diferentes tipos de Excepciones usando la resolución de sobrecarga, o para manejar la Excepción. Ambos tienen desventajas. Para empezar, esta es la manera de hacerlo con una función auxiliar:

private static bool Handle(Exception e) { Console.WriteLine("Handle the exception here :-)"); return true; // false will re-throw; } public static void Main() { try { throw new OutOfMemoryException(); } catch (ArgumentException e) { if (!Handle(e)) { throw; } } catch (IndexOutOfRangeException e) { if (!Handle(e)) { throw; } } Console.WriteLine("We''re done with the exception handling.");

Y la otra solución es atrapar el objeto Excepción y manejarlo en consecuencia. La traducción más literal para esto, basada en el contexto anterior es la siguiente:

try { throw new ArgumentException(); } catch (Exception e) { Exception ex = (Exception)(e as ArgumentException) ?? (e as IndexOutOfRangeException); if (ex != null) { Console.WriteLine("Handle the exception here :-)"); // throw ? } else { throw; } }

Así que para concluir:

  • Si no queremos volver a lanzar, podemos considerar capturar las excepciones correctas y almacenarlas de forma temporal.
  • Si el manejador es simple y queremos reutilizar el código, la mejor solución es probablemente introducir una función de ayuda.
  • Si queremos volver a lanzar, no tenemos más remedio que colocar el código en un controlador de captura ''Excepción'', que romperá FxCop y las excepciones no detectadas de su depurador.

En c # 6.0, los filtros de excepción son mejoras para el manejo de excepciones

try { DoSomeHttpRequest(); } catch (System.Web.HttpException e) { switch (e.GetHttpCode()) { case 400: WriteLine("Bad Request"); case 500: WriteLine("Internal Server Error"); default: WriteLine("Generic Error"); } }


Este es un problema clásico que cada desarrollador de C # enfrenta eventualmente.

Déjame dividir tu pregunta en 2 preguntas. El primero,

¿Puedo detectar múltiples excepciones a la vez?

En resumen, no.

Lo que lleva a la siguiente pregunta,

¿Cómo evito escribir código duplicado dado que no puedo detectar múltiples tipos de excepción en el mismo bloque catch ()?

Dada su muestra específica, donde el valor de repliegue es barato de construir, me gusta seguir estos pasos:

  1. Inicialice WebId al valor de retorno.
  2. Construye un nuevo Guid en una variable temporal.
  3. Establezca WebId en la variable temporal completamente construida. Haga de esta la declaración final del bloque try {}.

Entonces el código parece:

try { WebId = Guid.Empty; Guid newGuid = new Guid(queryString["web"]); // More initialization code goes here like // newGuid.x = y; WebId = newGuid; } catch (FormatException) {} catch (OverflowException) {}

Si se lanza una excepción, WebId nunca se establece en el valor a medio construir, y sigue siendo Guid.Empty.

Si construir el valor alternativo es costoso y restablecer un valor es mucho más barato, entonces movería el código de reinicio a su propia función:

try { WebId = new Guid(queryString["web"]); // More initialization code goes here. } catch (FormatException) { Reset(WebId); } catch (OverflowException) { Reset(WebId); }


Sólo tiene que llamar al try y atrapar dos veces.

try { WebId = new Guid(queryString["web"]); } catch (FormatException) { WebId = Guid.Empty; } try { WebId = new Guid(queryString["web"]); } catch (OverflowException) { WebId = Guid.Empty; }

¡¡Es así de sencillo!!


Actualización 2015-12-15: vea https://.com/a/22864936/1718702 para C # 6. Es un lenguaje más limpio y ahora estándar.

Dirigido a personas que desean una solución más elegante para atrapar una vez y filtrar excepciones, utilizo un método de extensión como se muestra a continuación.

Ya tenía esta extensión en mi biblioteca, escrita originalmente para otros propósitos, pero funcionó perfectamente para typeverificar las excepciones. Además, imho, parece más limpio que un montón de ||declaraciones. Además, a diferencia de la respuesta aceptada, prefiero el manejo explícito de excepciones, por lo que ex is ...tuve un comportamiento indeseable, ya que las clases derivadas se pueden asignar a los tipos principales.

Uso

if (ex.GetType().IsAnyOf( typeof(FormatException), typeof(ArgumentException))) { // Handle } else throw;

IsAnyOf.cs Extension (ver ejemplo de manejo de errores completo para dependencias)

namespace Common.FluentValidation { public static partial class Validate { /// <summary> /// Validates the passed in parameter matches at least one of the passed in comparisons. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="p_parameter">Parameter to validate.</param> /// <param name="p_comparisons">Values to compare against.</param> /// <returns>True if a match is found.</returns> /// <exception cref="ArgumentNullException"></exception> public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons) { // Validate p_parameter .CannotBeNull("p_parameter"); p_comparisons .CannotBeNullOrEmpty("p_comparisons"); // Test for any match foreach (var item in p_comparisons) if (p_parameter.Equals(item)) return true; // Return no matches found return false; } } }

Ejemplo completo de manejo de errores (copiar y pegar en una nueva aplicación de consola)

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Common.FluentValidation; namespace IsAnyOfExceptionHandlerSample { class Program { static void Main(string[] args) { // High Level Error Handler (Log and Crash App) try { Foo(); } catch (OutOfMemoryException ex) { Console.WriteLine("FATAL ERROR! System Crashing. " + ex.Message); Console.ReadKey(); } } static void Foo() { // Init List<Action<string>> TestActions = new List<Action<string>>() { (key) => { throw new FormatException(); }, (key) => { throw new ArgumentException(); }, (key) => { throw new KeyNotFoundException();}, (key) => { throw new OutOfMemoryException(); }, }; // Run foreach (var FooAction in TestActions) { // Mid-Level Error Handler (Appends Data for Log) try { // Init var SomeKeyPassedToFoo = "FooParam"; // Low-Level Handler (Handle/Log and Keep going) try { FooAction(SomeKeyPassedToFoo); } catch (Exception ex) { if (ex.GetType().IsAnyOf( typeof(FormatException), typeof(ArgumentException))) { // Handle Console.WriteLine("ex was {0}", ex.GetType().Name); Console.ReadKey(); } else { // Add some Debug info ex.Data.Add("SomeKeyPassedToFoo", SomeKeyPassedToFoo.ToString()); throw; } } } catch (KeyNotFoundException ex) { // Handle differently Console.WriteLine(ex.Message); int Count = 0; if (!Validate.IsAnyNull(ex, ex.Data, ex.Data.Keys)) foreach (var Key in ex.Data.Keys) Console.WriteLine( "[{0}][/"{1}/" = {2}]", Count, Key, ex.Data[Key]); Console.ReadKey(); } } } } } namespace Common.FluentValidation { public static partial class Validate { /// <summary> /// Validates the passed in parameter matches at least one of the passed in comparisons. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="p_parameter">Parameter to validate.</param> /// <param name="p_comparisons">Values to compare against.</param> /// <returns>True if a match is found.</returns> /// <exception cref="ArgumentNullException"></exception> public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons) { // Validate p_parameter .CannotBeNull("p_parameter"); p_comparisons .CannotBeNullOrEmpty("p_comparisons"); // Test for any match foreach (var item in p_comparisons) if (p_parameter.Equals(item)) return true; // Return no matches found return false; } /// <summary> /// Validates if any passed in parameter is equal to null. /// </summary> /// <param name="p_parameters">Parameters to test for Null.</param> /// <returns>True if one or more parameters are null.</returns> public static bool IsAnyNull(params object[] p_parameters) { p_parameters .CannotBeNullOrEmpty("p_parameters"); foreach (var item in p_parameters) if (item == null) return true; return false; } } } namespace Common.FluentValidation { public static partial class Validate { /// <summary> /// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails. /// </summary> /// <param name="p_parameter">Parameter to validate.</param> /// <param name="p_name">Name of tested parameter to assist with debugging.</param> /// <exception cref="ArgumentNullException"></exception> public static void CannotBeNull(this object p_parameter, string p_name) { if (p_parameter == null) throw new ArgumentNullException( string.Format("Parameter /"{0}/" cannot be null.", p_name), default(Exception)); } } } namespace Common.FluentValidation { public static partial class Validate { /// <summary> /// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="p_parameter">Parameter to validate.</param> /// <param name="p_name">Name of tested parameter to assist with debugging.</param> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name) { if (p_parameter == null) throw new ArgumentNullException("Collection cannot be null./r/nParameter_Name: " + p_name, default(Exception)); if (p_parameter.Count <= 0) throw new ArgumentOutOfRangeException("Collection cannot be empty./r/nParameter_Name: " + p_name, default(Exception)); } /// <summary> /// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails. /// </summary> /// <param name="p_parameter">Parameter to validate.</param> /// <param name="p_name">Name of tested parameter to assist with debugging.</param> /// <exception cref="ArgumentException"></exception> public static void CannotBeNullOrEmpty(this string p_parameter, string p_name) { if (string.IsNullOrEmpty(p_parameter)) throw new ArgumentException("String cannot be null or empty./r/nParameter_Name: " + p_name, default(Exception)); } } }

Dos pruebas unitarias NUnit de muestra

El comportamiento coincidente para los Exceptiontipos es exacto (es decir, un hijo NO ES una coincidencia para ninguno de sus tipos principales).

using System; using System.Collections.Generic; using Common.FluentValidation; using NUnit.Framework; namespace UnitTests.Common.Fluent_Validations { [TestFixture] public class IsAnyOf_Tests { [Test, ExpectedException(typeof(ArgumentNullException))] public void IsAnyOf_ArgumentNullException_ShouldNotMatch_ArgumentException_Test() { Action TestMethod = () => { throw new ArgumentNullException(); }; try { TestMethod(); } catch (Exception ex) { if (ex.GetType().IsAnyOf( typeof(ArgumentException), /*Note: ArgumentNullException derrived from ArgumentException*/ typeof(FormatException), typeof(KeyNotFoundException))) { // Handle expected Exceptions return; } //else throw original throw; } } [Test, ExpectedException(typeof(OutOfMemoryException))] public void IsAnyOf_OutOfMemoryException_ShouldMatch_OutOfMemoryException_Test() { Action TestMethod = () => { throw new OutOfMemoryException(); }; try { TestMethod(); } catch (Exception ex) { if (ex.GetType().IsAnyOf( typeof(OutOfMemoryException), typeof(Exception))) throw; /*else... Handle other exception types, typically by logging to file*/ } } } }


Entonces, ¿estás repitiendo muchos códigos dentro de cada cambio de excepción? Parece que extraer un método sería una idea divina, ¿no es así?

Así que su código se reduce a esto:

MyClass instance; try { instance = ... } catch(Exception1 e) { Reset(instance); } catch(Exception2 e) { Reset(instance); } catch(Exception) { throw; } void Reset(MyClass instance) { /* reset the state of the instance */ }

Me pregunto por qué nadie se dio cuenta de esa duplicación de código.

De C # 6, además, tiene los exception-filters como ya han mencionado otros. Así que puedes modificar el código anterior a esto:

try { ... } catch(Exception e) when(e is Exception1 || e is Exception2) { Reset(instance); }


Quería agregar mi respuesta corta a este hilo ya largo. Algo que no se ha mencionado es el orden de precedencia de las declaraciones de captura, más específicamente, debe conocer el alcance de cada tipo de excepción que está tratando de detectar.

Por ejemplo, si usa una excepción "catch-all" como Excepción , precederá a todas las demás declaraciones de captura y obviamente obtendrá errores de compilación. Sin embargo, si invierte el orden, puede encadenar sus declaraciones de captura (un poco de un anti-patrón, creo ) puede poner el tipo de Excepción catch-all en la parte inferior y esto capturará cualquier excepción que no se ajuste más arriba en su bloque try..catch:

try { // do some work here } catch (WebException ex) { // catch a web excpetion } catch (ArgumentException ex) { // do some stuff } catch (Exception ex) { // you should really surface your errors but this is for example only throw new Exception("An error occurred: " + ex.Message); }

Recomiendo mucho a la gente que revise este documento de MSDN:

Jerarquía de excepciones


Tenga en cuenta que encontré una forma de hacerlo, pero esto se parece más al material de The Daily WTF :

catch (Exception ex) { switch (ex.GetType().Name) { case "System.FormatException": case "System.OverflowException": WebId = Guid.Empty; break; default: throw; } }


catch (Exception ex) { if (!( ex is FormatException || ex is OverflowException)) { throw; } Console.WriteLine("Hello"); }