¿Capturas múltiples excepciones a la vez?

2140

Se desaconseja simplemente atrapar System.Exception. En cambio, solo se deben detectar 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 alguna forma de detectar ambas excepciones y solo llamar a la WebId = Guid.Emptyllamada una vez?

El ejemplo dado es bastante simple, ya que es solo a 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 "restablecer" el object. Sin embargo, si hay una excepción inesperada, todavía quiero lanzar eso más alto.

Michael Stum
fuente
55
Si está utilizando .net 4 y superior, prefiero usar
agregateexception
2
Desde Bepenfriends- System.Guid no lanza AggregateException , sería grande si usted (o alguien) puede publicar una respuesta que muestra cómo se envuelve en una AggregateException etc ..
vertedero
1
Sobre el uso AggregateException: Lanzar una AggregateException en mi propio código
DavidRR
11
"Se desaconseja simplemente atrapar System.Exception". -y si el método puede arrojar 32 tipos de excepciones, ¿qué hace? escribir captura para cada uno de ellos por separado?
giorgim
55
Si un método arroja 32 tipos diferentes de excepciones, está mal escrito. No está captando excepciones que están haciendo sus propias llamadas, está haciendo MUCHO demasiado en un método, o la mayoría / todos esos 32 deberían ser una sola excepción con un código de razón.
Flynn1179

Respuestas:

2100

Captura System.Exceptiony enciende los tipos

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

    throw;
}
Joseph Daigle
fuente
69
Desafortunadamente, a FxCop (es decir, Visual Studio Code Analysis) no le gusta cuando detecta Exception.
Andrew Garrison
15
Estoy de acuerdo con no capturar la excepción, pero, en este caso, la captura es un filtro. Puede tener una capa más alta que manejará otros tipos de excepciones. Yo diría que esto es correcto, aunque incluye una captura (Excepción x). No modifica el flujo del programa, solo maneja ciertas excepciones y luego permite que el resto de la aplicación se ocupe de cualquier otro tipo de excepción.
lkg
28
La última versión de FxCop no produce una excepción cuando se utiliza el código anterior.
Peter
28
No estoy seguro de qué estaba mal con el código del OP en primer lugar. La respuesta # 1 aceptada es casi el doble de líneas y mucho menos legible.
João Bragança
22
@ JoãoBragança: Si bien esta respuesta en este ejemplo usa más líneas, trate de imaginar si está tratando con un archivo IO, por ejemplo, y todo lo que quiere hacer es detectar esas excepciones y hacer algunos mensajes de registro, pero solo aquellos que espera que provengan de su archivo de métodos IO. Luego, a menudo tiene que lidiar con un número mayor (aproximadamente 5 o más) de diferentes tipos de excepciones. En esa situación, este enfoque puede ahorrarle algunas líneas.
Xilconic
595

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

Excepto que todavía odio el diseño de una línea larga y personalmente presentaría el código de la siguiente manera. 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 humo santo ...

Pasando directamente a la persecución, este tipo de duplica una respuesta anterior, pero si realmente desea realizar una acción común para varios tipos de excepción y mantener todo ordenado y ordenado dentro del alcance de un método, ¿por qué no usar una 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 que ese cierre sea un método separado que puedas utilizar en todo el lugar. Pero entonces será muy fácil hacerlo sin cambiar realmente el resto del código estructuralmente. ¿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é hacer todo este esfuerzo 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 código de olor, quiero decir ejemplo, solo para fingir que estás guardando algunas 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.

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

Pero ese es mi punto. Ustedes han oído hablar de funciones / métodos, ¿verdad? Seriamente. Escriba una ErrorHandlerfunción común y, como, llámela desde cada bloque catch.

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

La fase de mantenimiento, para cualquiera que sea relativamente nuevo en la programación, comprenderá el 98.7% o más de la vida útil general de su proyecto, y el pobre imbécil que hace el mantenimiento seguramente será alguien más que usted. Y es muy probable que pasen el 50% de su tiempo en el trabajo maldiciendo su nombre.

Y, por supuesto, FxCop te ladra, por lo que también debes agregar un atributo a tu código que tenga que ver exactamente 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 marcar. Y, lo siento, podría estar equivocado, pero ¿ese atributo de "ignorar" no termina realmente compilado en su aplicación?

¿Poner la ifprueba completa en una línea la haría más legible? No lo creo. Quiero decir, hace mucho 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 cual fue un desafío) cómo el intérprete o el compilador dividiría esa larga línea en declaraciones discretas de una instrucción por línea, esencialmente idénticas al resultado si hubiera seguido adelante y solo hizo que el código fuera legible en lugar de tratar de superar al compilador, no tuvo ningún efecto en él. Pero yo divago.

¿Cuánto menos legible es esto cuando agrega tres tipos de excepción más, dentro de un mes o dos? (Respuesta: se vuelve mucho menos legible).

Uno de los puntos principales, realmente, es que la mayoría del punto de formatear el 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 también apesta totalmente.

Solo digo...

// super sucks...
catch( Exception ex )
{
    if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
    {
        // write to a log, whatever...
        return;
    }
    throw;
}
Craig
fuente
36
Cuando me topé por primera vez con esta pregunta, supe por completo la respuesta aceptada. Genial, solo puedo atrapar todos los Exceptionsy verificar el tipo. Pensé que limpiaba el código, pero algo me mantuvo volviendo a la pregunta y realmente leí las otras respuestas a la pregunta. Lo mordí por un tiempo, pero tengo que estar de acuerdo contigo. Es más fácil de leer y mantener usar una función para secar su código que para atrapar todo, verificar el tipo comparándolo con una lista, envolviendo código y arrojando. Gracias por llegar tarde y proporcionar una opción alternativa y sensata (IMO). +1.
erróneo
8
Usar una función de manejo de errores no funcionaría si quisieras incluir a throw;. Tendría que repetir esa línea de código en cada bloque catch (obviamente no es el fin del mundo, pero vale la pena mencionarlo, ya que es un código que debería repetirse).
kad81
55
@ kad81, eso es cierto, pero aún obtendría el beneficio de escribir el código de registro y limpieza en un lugar, y cambiarlo en un lugar si es necesario, sin la semántica tonta de atrapar el tipo de excepción base y luego bifurcar en función de El tipo de excepción. Y esa throw();declaración adicional en cada bloque de captura es un pequeño precio a pagar, IMO, y aún lo deja en la posición de hacer una limpieza adicional específica del tipo de excepción si es necesario.
Craig
2
Hola @Reitffunk, solo usa en Func<Exception, MyEnumType>lugar de Action<Exception>. Eso es Func<T, Result>, con Resultser el tipo de retorno.
Craig
3
Estoy completamente de acuerdo aquí. Yo también leí la primera respuesta y el pensamiento parece lógico. Movido a un genérico 1 para todos los manejadores de excepciones. Algo dentro de mí me hizo vomitar internamente ... así que revirtí el código. Luego se encontró con esta belleza! Esta debe ser la respuesta aceptada
Conor Gallagher
372

Como otros han señalado, puede tener una ifdeclaración 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 MyFiltermétodo 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 ifdeclaración desde dentro del catchbloque, usar filtros de excepción no desenrollará la pila.

Puede descargar Visual Studio 2015 para ver esto.

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

Install-Package Microsoft.Net.Compilers

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

Hacer referencia a este paquete hará que el proyecto se construya 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.

Joe
fuente
3
Esperando pacientemente el lanzamiento oficial de 6 ... Me gustaría ver que esto se ponga de manifiesto cuando eso suceda.
RubberDuck
@RubberDuck Me muero por el operador de propagación nulo de C # 6. Intento convencer al resto de mi equipo de que el riesgo de un lenguaje / compilador inestable vale la pena. Muchas mejoras menores con gran impacto. En cuanto a ser marcado como respuesta, no es importante, siempre y cuando la gente se dé cuenta de que esto será posible, estoy feliz.
Joe
¡¿Derecha?! Voy a echar un buen vistazo a mi base de código en un futuro próximo. =) Sé que la verificación no es importante, pero dado que la respuesta aceptada pronto quedará desactualizada, espero que OP regrese para verificar esto para darle la visibilidad adecuada.
RubberDuck
Eso es en parte por qué aún no lo he otorgado @Joe. Quiero que esto sea visible. Sin embargo, es posible que desee agregar un ejemplo de filtro en línea para mayor claridad.
RubberDuck
188

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

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 llamarlo en esos bloques específicos:

Action onError = () => WebId = Guid.Empty;
try
{
    // something
}
catch (FormatException)
{
    onError();
}
catch (OverflowException)
{
    onError();
}
Greg Beech
fuente
26
Idea interesante y otro ejemplo de que VB.net tiene algunas ventajas interesantes sobre C # a veces
Michael Stum
47
@MichaelStum con ese tipo de sintaxis, difícilmente lo llamaría interesante ... estremecimiento
MarioDS
17
¡Los filtros de excepción vienen en c # 6! Tenga en cuenta la diferencia de usar filtros a favor de volver a lanzar roslyn.codeplex.com/discussions/541301
Arne Deruwe
@ArneDeruwe ¡Gracias por ese enlace! Acabo de aprender una razón más importante para no volver a tirar: throw e;destruye stacktrace y callstack, throw;destruye "solo" callstack (¡inutilizando los volcados de memoria!) ¡Una muy buena razón para no usar ninguno si se puede evitar!
AnorZaken
1
¡A partir de C # 6 hay filtros de excepción disponibles! Finalmente.
Danny
134

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

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

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


Desde C # 7 , puede 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 se implementa la deconstrucción de parámetros externos en C # 12. :)

Athari
fuente
19
Precisamente, conciso, y omite totalmente la penalización de rendimiento de manejar la excepción, la mala forma de usar excepciones intencionalmente para controlar el flujo del programa y el enfoque suave de tener su lógica de conversión extendida, un poco aquí y un poco allí .
Craig
99
Sé lo que querías decir, pero por supuesto Guid.TryParsenunca regresa Guid.Empty. Si la cadena tiene un formato incorrecto, establece el resultparámetro de salida en Guid.Empty, pero regresa false . Lo menciono porque he visto código que hace cosas al estilo de Guid.TryParse(s, out guid); if (guid == Guid.Empty) { /* handle invalid s */ }, lo que generalmente es incorrecto si spudiera ser la representación de cadena Guid.Empty.
14
wow has respondido la pregunta, excepto que no está dentro del espíritu de la pregunta. El problema más grande es otra cosa :(
nawfal
66
El patrón adecuado para usar TryParse, por supuesto, es más parecido if( Guid.TryParse(s, out guid){ /* success! */ } else { /* handle invalid s */ }, lo que no deja ambigüedad como el ejemplo roto donde el valor de entrada podría ser la representación de cadena de un Guid.
Craig
2
Esta respuesta puede ser correcta con respecto a Guid.Parse, pero se ha perdido todo el punto de la pregunta original. Lo que no tenía nada que ver con Guid.Parse, pero estaba relacionado con la captura de Exception vs FormatException / OverflowException / etc.
Conor Gallagher
115

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

En C # 7.0+, también puede combinar esto con la coincidencia de patrones

try
{
   await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae &&
                           ae.InnerExceptions.Count > tasks.Count/2)
{
   //More than half of the tasks failed maybe..? 
}
Mat J
fuente
Se prefiere este método no solo porque es simple y claro, sino que no tiene que desenrollar la pila si no se cumplen las condiciones, lo que proporciona un mejor rendimiento e información de diagnóstico en comparación con la repetición.
Joe
74

Si puede actualizar su aplicación a C # 6, tiene suerte. La nueva versión de C # ha implementado filtros de excepción. Entonces 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. Primero, un nuevo lanzamiento significa más sobrecarga que saltarse la captura. En segundo lugar, 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 por caída es menos útil o incluso inútil.

Vea una discusión sobre esto en CodePlex . Y un ejemplo que muestra la diferencia .

Maniero
fuente
44
Lanzar sin excepción conserva la pila, pero "throw ex" la sobrescribirá.
Ivan
32

Si no desea utilizar una ifdeclaración dentro de los catchalcances, en la C# 6.0que puede utilizar Exception Filtersla sintaxis que ya fue apoyada por el CLR en versiones vistas previas, pero sólo 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 capturará el Exceptionúnico cuando es un InvalidDataExceptiono ArgumentNullException.

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

static int a = 8;

...

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

Tenga en cuenta que, a diferencia de una ifdeclaración dentro del catchalcance del 's, Exception Filtersno se puede lanzar Exceptions, y cuando lo hacen, o cuando la condición no es true, la siguiente catchcondición se evaluará en su lugar:

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 true Exception Filteruno, 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 el MSILcódigo, el código no se traduce a ifdeclaraciones, sino a Filters, y Exceptionsno se puede lanzar desde las áreas marcadas con Filter 1y, Filter 2pero el filtro que arroja Exceptionfallará, también el último valor de comparación empujado a la pila antes del endfiltercomando determinará el éxito / falla del filtro ( Catch 1 XOR Catch 2 se ejecutará en consecuencia):

Filtros de excepción MSIL

Además, específicamente Guidtiene el Guid.TryParsemétodo.

Tamir Vered
fuente
+1 para mostrar múltiples filtros cuando y proporcionar una explicación de lo que sucede cuando se utilizan filtros múltiples.
steven87vt
26

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

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

Y con C # 8 como expresión de cambio:

catch (Exception ex)
{
    WebId = ex switch
    {
        _ when ex is FormatException || ex is OverflowException => Guid.Empty,
        _ => throw ex
    };
}
Fabian
fuente
3
Esta debería ser la respuesta aceptada a partir de 2018 en mi humilde opinión.
MemphiZ
66
La respuesta de Mat J usando whenes mucho más elegante / apropiada que un interruptor.
rgoliveira
@rgoliveira: Estoy de acuerdo en que para el caso que se hizo en la pregunta, la respuesta de Mat J es más elegante y apropiada. Sin embargo, se hace difícil de leer si tiene un código diferente que desea ejecutar según el tipo de excepción o si realmente desea utilizar la instancia de la excepción. Todos estos escenarios se pueden tratar por igual con esta declaración de cambio.
Fabian
1
@Fabian "si tiene un código diferente que desea ejecutar según el tipo de excepción o si realmente desea usar la instancia de la excepción", entonces simplemente crea un catchbloque diferente , o de todos modos necesitaría lanzarlo. En mi experiencia, un throw;en su catchbloque es probablemente un olor a código.
rgoliveira
@rgoliveira: Usar un lanzamiento en un bloque catch está bien en varios casos, ver el enlace . Dado que la declaración de caso realmente usa un enlace de coincidencia de patrones , no es necesario convertir si sustituye el enlace del operador de descarte (el guión bajo) por un nombre de variable. No me malinterpreten, estoy de acuerdo con ustedes en que los filtros de excepción son una forma más limpia de hacerlo, pero los múltiples bloques de captura agregan muchos corchetes.
Fabian
20

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 ligeramente el rendimiento.

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

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

bool exThrown = false;

try
{
    // Something
}
catch (FormatException) {
    exThrown = true;
}
catch (OverflowException) {
    exThrown = true;
}

if (exThrown)
{
    // Something else
}
Mate
fuente
19
Pero tenga en cuenta que no puede volver a lanzar la excepción sin perder el seguimiento de la pila si lo hace así. (Ver el comentario de Michael Stum a la respuesta aceptada)
René
2
Este patrón se puede mejorar almacenando la excepción (disculpe el mal formato; no puedo entender cómo poner código en los comentarios): Excepción ex = nulo; pruebe {// something} catch (FormatException e) {ex = e; } catch (OverflowException e) {ex = e; } if (ex! = null) {// algo más y tratar con ex}
Jesse Weigert
3
@JesseWeigert: 1. Puede usar las teclas de retroceso para dar a una parte de texto una fuente monoespaciada y un fondo gris claro. 2. Aún no podrá volver a lanzar la excepción original, incluido el stacktrace .
Oliver
2
@CleverNeologism, aunque puede ser cierto que el uso del isoperador puede tener un ligero impacto negativo en el rendimiento, también es cierto que un controlador de excepciones no es el lugar para preocuparse demasiado por optimizar el rendimiento. Si su aplicación está pasando tanto tiempo en controladores de excepciones que la optimización del rendimiento allí marcaría una diferencia real en el rendimiento de la aplicación, entonces hay otros problemas de código a tener en cuenta. Dicho esto, todavía no me gusta esta solución porque pierdes el seguimiento de la pila y porque la limpieza se elimina contextualmente de la declaración catch.
Craig
3
El único momento en que el isoperador degrada el rendimiento es si luego realiza una asoperación (por lo tanto, califican la regla innecesariamente ). Si todo lo que está haciendo es probar el yeso sin necesidad de realizarlo, entonces el isoperador es exactamente lo que desea usar.
Salsa
19

en C # 6, el enfoque recomendado es usar 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");
 }
SHM
fuente
18

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án otras excepciones y no se aplicará el código WebId = Guid.Empty;. 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
}
bsara
fuente
-1 Esto se ejecutará WebId = Guid.Emtpyen el caso de que no se haya producido ninguna excepción.
Sepster
44
@sepster Creo que la declaración de devolución después de "// algo" está implícita aquí. Realmente no me gusta la solución, pero esta es una variante constructiva en la discusión. +1 para deshacer su voto negativo :-)
toong
@Sepster toong tiene razón, supuse que si querías un retorno allí, entonces pondrías uno ... Estaba tratando de hacer mi respuesta lo suficientemente general como para aplicarla a todas las situaciones en caso de que otras con preguntas similares pero no exactas se beneficiarían como bien. Sin embargo, en buena medida, agregué un returna mi respuesta. Gracias por el aporte.
bsara
18

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 'arrojar' o 'devolver' que en la solución de Joseph están separadas de la expresión.

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

catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

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

Así es como 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
}
Stefan T
fuente
2
+1, esta es la mejor respuesta. Es mejor que la respuesta principal principalmente porque no hay return, aunque invertir la condición también es un poco mejor.
DCShannon
Ni siquiera pensé en eso. Buena captura, lo agregaré a la lista.
Stefan T
16

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

FlySwat
fuente
21
¿Por qué no usar la palabra clave "es"?
Chris Pietschmann
29
@Michael: si Microsoft introdujo, digamos, StringTooLongException derivada de FormatException, entonces sigue siendo una excepción de formato, solo una específica. Depende de si desea la semántica de 'capturar este tipo de excepción exacto' o 'capturar excepciones que significan que el formato de la cadena era incorrecto'.
Greg Beech
66
@Michael - Además, tenga en cuenta que "catch (FormatException ex) tiene la última semántica, capturará todo lo derivado de FormatException.
Greg Beech
14
@Alex No. "throw" sin "ex" lleva la excepción original, incluido el seguimiento de pila original, hacia arriba. Agregar "ex" restablece el seguimiento de la pila, por lo que realmente obtiene una excepción diferente a la original. Estoy seguro de que alguien más puede explicarlo mejor que yo. :)
Samantha Branham
13
-1: Este código es extremadamente frágil - un desarrollador biblioteca podría esperar para reemplazar throw new FormatException();con throw new NewlyDerivedFromFormatException();y sin romper el código usando la biblioteca, y será válido para todo el manejo de casos, excepto cuando alguien utiliza una excepción ==en lugar de is(o, simplemente,catch (FormatException) ).
Sam Harwell
13

Qué tal si

try
{
    WebId = Guid.Empty;
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
Maurice
fuente
Eso funciona solo si el Catch-Code puede moverse completamente al Try-Block. Pero el código de imagen donde realiza múltiples manipulaciones a un objeto, y uno en el medio falla, y desea "restablecer" el objeto.
Michael Stum
44
En ese caso, agregaría una función de reinicio y llamaría desde múltiples bloques catch.
Maurice
12
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}
Konstantin Spirin
fuente
11

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

Lo que está en el enlace no responde su pregunta directamente, pero es trivial extenderlo para que se vea así:

static void Main() 
{ 
    Action body = () => { ...your code... };

    body.Catch<InvalidOperationException>() 
        .Catch<BadCodeException>() 
        .Catch<AnotherException>(ex => { ...handler... })(); 
}

(Básicamente, proporcione otra Catchsobrecarga vacía que se devuelva)

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

nawfal
fuente
1
Una posible ventaja de este enfoque es que existe una diferencia semántica entre atrapar y volver a lanzar una excepción versus no atraparla; en algunos casos, el código debe actuar sobre una excepción sin atraparlo. Tal cosa es posible en vb.net, pero no en C # a menos que uno use un contenedor escrito en vb.net y llamado desde C #.
supercat
1
¿Cómo actúa en una excepción sin atraparla? No te entiendo completamente.
nawfal
@nawful ... usando el filtro vb - función filt (ej como excepción): LogEx (ex): devuelve false ... luego en la línea de captura: catch ex when filt (ex)
FastAl
1
@FastAl ¿No es esto lo que permiten los filtros de excepción en C # 6?
HimBromBeere
@HimBromBeere sí, son análogos directos
FastAl
9

Actualización 2015-12-15: Consulte https://stackoverflow.com/a/22864936/1718702 para C # 6. Es un limpiador y ahora estándar en el lenguaje.

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, originalmente escrita para otros fines, pero funcionó perfectamente para typeverificar las excepciones. Además, en mi opinión, se ve 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;

Extensión IsAnyOf.cs (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 la 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 muestras de pruebas de unidad NUnit

El comportamiento de coincidencia 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(StackOverflowException)))
                    throw;

                /*else... Handle other exception types, typically by logging to file*/
            }
        }
    }
}
HodlDwon
fuente
1
Mejorar el idioma no es "más elegante". En muchos lugares, esto realmente creó un infierno de mantenimiento. Años después, muchos programadores no están orgullosos del monstruo que crearon. No es lo que estás acostumbrado a leer. Puede causar un "¿eh?" efecto, o incluso "WTF" graves. Es confuso, a veces. Lo único que hace es hacer que el código sea mucho más difícil de comprender para aquellos que necesitan lidiar con él más adelante en el mantenimiento, solo porque un solo programador trató de ser "inteligente". Con los años, aprendí que esas soluciones "inteligentes" rara vez son también las buenas.
Kaii
1
o en pocas palabras: quédese con las posibilidades que el lenguaje ofrece de forma nativa. no intenta reemplazar la semántica de un lenguaje, sólo porque usted no lo hace como ellos. Tus colegas (y posiblemente mi futuro) te lo agradecerán, sinceramente.
Kaii
También tenga en cuenta que su solución solo se aproxima a la semántica de C # 6 when, como lo haría con cualquier versión de catch (Exception ex) {if (...) {/*handle*/} throw;}. El valor real de whenes que el filtro se ejecuta antes de que se detecte la excepción , evitando así la corrupción de gastos / apilamiento de un nuevo lanzamiento. Aprovecha una función CLR que solo era accesible anteriormente para VB y MSIL.
Marc L.
¿Mas elegante? Este ejemplo es tan grande para un problema tan simple y el código es tan horrible que ni siquiera valió la pena echarle un vistazo. No haga de este código el problema de otra persona en un proyecto real.
KthProg
todo su IsAnyOfmétodo se puede reescribir simplementep_comparisons.Contains(p_parameter)
maksymiuk
7

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, digamos:

// 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 capture cosas que necesitamos más adelante en el proceso. Claro, podemos detectar una excepción y verificar con un 'si' qué hacer, pero seamos honestos, realmente no queremos eso. (FxCop, problemas del depurador, 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 la Partición II de MS, los bloques del controlador de excepciones IL no funcionarán así, lo que en este caso tiene sentido porque eso implicaría que el objeto '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 parecido 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 se almacenan aquí en las variables 'e')? La forma en que queremos que el compilador maneje esto es notar que el tipo base común de ambas excepciones es 'Excepción', use eso para que una variable contenga ambas excepciones, y luego maneje solo las dos excepciones que están atrapadas. 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 'Excepción' correcto. 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 tirar adecuadamente y, bueno, seamos honestos, es una solución bastante fea. La fealdad se puede arreglar un poco realizando 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-tiro'. Para que esto funcione, debemos ser capaces de realizar el manejo dentro del bloque 'catch', y la única forma de hacer que esto funcione es mediante la captura de un objeto 'Exception'.

En este punto, podemos agregar una función separada que maneja los diferentes tipos de Excepciones usando la resolución de sobrecarga, o para manejar la Excepción. Ambos tienen desventajas. Para comenzar, esta es la forma 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 capturar el objeto Exception y manejarlo en consecuencia. La traducción más literal para esto, basada en el contexto anterior es esta:

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

Entonces para concluir:

  • Si no queremos volver a lanzar, podríamos considerar capturar las excepciones correctas y almacenarlas de manera temporal.
  • Si el controlador es simple, y queremos reutilizar el código, la mejor solución es probablemente introducir una función auxiliar.
  • Si queremos volver a lanzar, no tenemos más remedio que poner el código en un controlador de captura 'Excepción', que romperá FxCop y las excepciones no capturadas de su depurador.
atlaste
fuente
7

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

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

¿Puedo atrapar varias 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 atrapar varios tipos de excepción en el mismo bloque catch ()?

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

  1. Inicialice WebId en el valor de reserva.
  2. Construya un nuevo Guid en una variable temporal.
  3. Establezca WebId en la variable temporal totalmente construida. Haga de esta la declaración final del bloque try {}.

Entonces el código se ve así:

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 produce alguna excepción, WebId nunca se establece en el valor medio construido y permanece Guid.Empty.

Si construir el valor de reserva 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);
}
Jeffrey Rennie
fuente
Esto es bueno, "codificación ecológica", es decir, está pensando con anticipación en su huella de código y datos y asegurándose de que no se filtren los valores medio procesados. Agradable seguir este patrón gracias Jeffrey!
Tahir Khalid
6

Entonces, ¿estás repitiendo mucho código dentro de cada cambio de excepción? Parece que extraer un método sería una buena idea, ¿no?

Entonces 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 notó esa duplicación de código.

Desde C # 6, además, tiene los filtros de excepción como ya han mencionado otros. Para que pueda modificar el código anterior a esto:

try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{ 
    Reset(instance); 
}
HimBromBeere
fuente
3
"Me pregunto por qué nadie notó esa duplicación de código". - ¿Cómo? El objetivo de la pregunta es eliminar la duplicación de código.
Mark Amery
4

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 catch, 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 otras declaraciones catch y obviamente obtendrá errores del compilador; sin embargo, si invierte el orden, puede encadenar sus declaraciones catch (un poco de antipatrón, creo ) puede colocar el tipo de excepción general en la parte inferior y esto capturará las excepciones que no se adaptaron 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 encarecidamente que la gente revise este documento de MSDN:

Jerarquía de excepciones

Tahir Khalid
fuente
4

¿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()
{
    // ...
}

Cómo lo haría, tratar de encontrar el patrón simple es hermoso

Żubrówka
fuente
3

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

catch (Exception ex)
{
    switch (ex.GetType().Name)
    {
        case "System.FormatException":
        case "System.OverflowException":
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}
Michael Stum
fuente
99
-1 voto, +5 WTF :-) Esto no debería haber sido marcado como respuesta, pero es grave.
Aaron
1
No importa cuán simplemente podamos hacerlo. Pero no se quedó inactivo y se le ocurrió su punto de vista para resolverlo. En verdad lo aprecio.
Maxymus
2
Sin embargo, en realidad no haga esto, use los filtros de excepción en C # 6 o cualquiera de las otras respuestas. Puse esto aquí específicamente como "Esta es una forma, pero es malo y quiero hacer algo mejor".
Michael Stum
¿Por qué es esto malo? Me sorprendió que no pudieras usar la excepción en una declaración de cambio directamente.
MKesper
3
@MKesper Veo algunas razones por las que es malo. Requiere escribir los nombres de clase totalmente calificados como literales de cadena, lo cual es vulnerable a errores tipográficos de los que el compilador no puede salvarte. (Esto es significativo ya que en muchas tiendas los casos de error están menos probados y, por lo tanto, es más probable que se omitan los errores triviales en ellos). Tampoco coincidirá con una excepción que es una subclase de uno de los casos especificados. Y, debido a que son cadenas, los casos se perderán con herramientas como "Buscar todas las referencias" de VS, pertinente si desea agregar un paso de limpieza en todas partes donde se detecte una excepción particular.
Mark Amery
2

Vale la pena mencionar aquí. Puede responder a las múltiples combinaciones (error de excepción y mensaje de excepción).

Me encontré con un escenario de caso de uso al intentar emitir un objeto de control en una cuadrícula de datos, con contenido como TextBox, TextBlock o CheckBox. En este caso, la excepción devuelta fue la misma, pero el mensaje varió.

try
{
 //do something
}
catch (Exception ex) when (ex.Message.Equals("the_error_message1_here"))
{
//do whatever you like
} 
catch (Exception ex) when (ex.Message.Equals("the_error_message2_here"))
{
//do whatever you like
} 
Jorge
fuente
0

Quiero sugerir la respuesta más corta (un estilo funcional más ):

        Catch<FormatException, OverflowException>(() =>
            {
                WebId = new Guid(queryString["web"]);
            },
            exception =>
            {
                WebId = Guid.Empty;
            });

Para esto, debe crear varias sobrecargas del método "Catch", similares a System.Action:

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2));
    }

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2, TException3>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2), typeof(TException3));
    }

y así tantos como desees. Pero debe hacerlo una vez y puede usarlo en todos sus proyectos (o, si creó un paquete nuget, podríamos usarlo también).

Y la implementación de CatchMany:

    [DebuggerNonUserCode]
    public static void CatchMany(Action tryBlock, Action<Exception> catchBlock,
        params Type[] exceptionTypes)
    {
        try
        {
            tryBlock();
        }
        catch (Exception exception)
        {
            if (exceptionTypes.Contains(exception.GetType())) catchBlock(exception);
            else throw;
        }
    }

pd No he realizado comprobaciones nulas para la simplicidad del código, considere agregar validaciones de parámetros.

ps2 Si desea devolver un valor de la captura, es necesario hacer los mismos métodos de captura, pero con devoluciones y Func en lugar de acción en los parámetros.

Eugene Gorbovoy
fuente
-15

Simplemente llame al intento y atrape 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!!

chico erudito
fuente
3
um Esto está frustrando el propósito de la pregunta. Él hace esta pregunta para deshacerse del código duplicado. Esta respuesta agrega más código duplicado.
James Esh
-23

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");
    }
}
Kashif
fuente
13
Este ejemplo no muestra ningún uso de filtros de excepción.
user247702
Esta es la forma estándar de filtrar excepciones en c # 6.0
Kashif
55
Eche un vistazo nuevamente a qué son exactamente los filtros de excepción. No está utilizando un filtro de excepción en su ejemplo. Hay un ejemplo apropiado en esta respuesta publicado un año antes que el tuyo.
user247702
66
Un ejemplo de filtrado de excepción seríacatch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }
saluce