Pregunta

No se recomienda simplemente capturar System.Exception . En su lugar, solo el " conocido " las excepciones deben ser capturadas.

Ahora, esto conduce a veces a códigos repetitivos innecesarios, por ejemplo:

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

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

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

¿Fue útil?

Solución

Capture System.Exception y active los tipos

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

    throw;
}

Otros consejos

EDITAR: estoy de acuerdo con otras personas 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 es ...)

Excepto que todavía sigo odiando 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 ...

Cortando directamente a la persecución, este tipo duplica 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 único, ¿por qué no? ¿Usa una función lambda / clausura / 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 cualquier lugar. Pero luego será muy fácil hacerlo sin cambiar realmente estructuralmente el resto del código. Derecho?

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 dejar de preguntarme ( advertencia: un poco de ironía / sarcasmo en el futuro) por qué demonios se esfuerzan por reemplazar 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ás 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.

Por supuesto, dejé las tres instancias idénticas de / * escribir en un registro, lo que sea ... * / return; 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 común de ErrorHandler y, por ejemplo, llámela desde cada bloque catch.

Si me preguntas, el segundo ejemplo (con las palabras clave if y son ) es significativamente menos legible y, al mismo tiempo, mucho más propenso a errores durante la fase de mantenimiento de tu 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 hace el mantenimiento es casi seguro que será otra persona que usted. . Y es muy probable que pasen el 50% de su tiempo en el trabajo maldiciendo tu nombre.

Y, por supuesto, FxCop te ladra y, por lo tanto, debes also agregar un atributo a tu código que tenga un código postal preciso que ver con el programa en ejecución, y solo está hay que 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 eso no hace que " ignore " ¿Los atributos terminan realmente compilados en tu aplicación?

¿Poner todo el test si en una línea lo haría más legible? No lo creo. Quiero decir, tuve otro programador que argumentó con vehemencia una vez hace mucho tiempo que poner más código en una línea haría que "se ejecutara más rápido". Pero, por supuesto, estaba completamente loco. Intentando 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 estoy divagando.

¿Cuánto se puede leer con menos cuando agrega tres tipos de excepción más, uno o dos meses a partir de ahora? (Respuesta: se vuelve lot 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 está sucediendo cuando el código se ejecuta. 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. Por lo tanto, todo en una línea también apesta totalmente.

Sólo digo ...

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

Como han señalado otros, puede tener una declaració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, esto se puede hacer todo 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 declaración if dentro del bloque catch , usar filtros de excepción no desenrollará la pila.

Puede descargar Visual Studio 2015 para verificar esto fuera.

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.

  

Al hacer referencia a este paquete, el proyecto se creará utilizando el   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.

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

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

Lo que podrías hacer es usar una función anónima para encapsular tu 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 integridad, ya que .NET 4.0 el código se puede reescribir como:

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

TryParse nunca arroja excepciones y devuelve false si el formato es incorrecto, al configurar 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 las 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. :)

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 saltarse la captura. Segundo, no es semánticamente equivalente. La nueva característica conserva la pila intacta cuando está depurando su código. Sin esta función, el volcado de caída es menos útil o incluso inútil.

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

Los filtros de excepción ahora están disponibles en c # 6+. Usted puede 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 puedes combinar esto con la coincidencia de patrones

try
{
   await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae )
{
   //do something with members of ae, say ae.InnerExceptions
}

Si no desea utilizar una declaración if dentro de los ámbitos catch , en C # 6.0 usted puede usar la sintaxis de Filtros de excepción que ya era compatible con el CLR en las versiones previas, pero 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 sea una InvalidDataException o ArgumentNullException .

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

static int a = 8;

...

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

Tenga en cuenta que, a diferencia de una declaración if dentro del alcance de catch , Filtros de excepción no puede lanzar Excepciones , 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 Filtro de excepción , 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 se puede ver en el MSIL , el código no se traduce a las declaraciones de if , sino a Filters y Exceptions no puede lanzarse desde las áreas marcadas con Filter 1 y Filter 2 pero el filtro que lanza la Exception también fallará, también el último valor de comparación introducido en la pila antes de que el comando endfilter determinará el éxito / fracaso del filtro ( Catch 1 XOR Catch 2 se ejecutará en consecuencia):

 Filtros de excepción MSIL

También, específicamente Guid tiene el Guid.TryParse .

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

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

CA1800: No lance innecesariamente dice que " considere probar el resultado del operador 'as' en lugar de " ;, 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 haría:

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}

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

Con C # 7 la respuesta de Michael Stum puede mejorarse 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;
    }
}

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 se verá afectado. 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 de Joseph Daigle es una buena solución, pero encontré la siguiente estructura será 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
}

Editar: El filtrado de excepciones en C # 6.0 hará que la sintaxis sea un poco más limpia y viene con un número de otros beneficios sobre cualquier solución actual. (notablemente sin dañar la pila)

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
}

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

¿Qué tal

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

Precaución y advertencia: Otro tipo más , estilo funcional.

Lo que se encuentra en el enlace no responde tu pregunta directamente, pero es trivial extenderlo 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 que se devuelve a sí misma)

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

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

Actualización 2015-12-15: consulte https://stackoverflow.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 capturar una vez y filtrar excepciones, utilizo un método de extensión como se demuestra abajo.

Ya tenía esta extensión en mi biblioteca, escrita originalmente para otros propósitos, pero funcionó perfectamente para el tipo que verifica las excepciones. Además, imho, 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 ej es ... tuvo un comportamiento no deseado, ya que las clases derivadas se pueden asignar a los tipos principales).

Usuario

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

IsAnyOf.cs Extension (ver ejemplo de manejo de errores completos 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 de manejo completo 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 pruebas unitarias NUnit de muestra

El comportamiento de coincidencia para los tipos Exception 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*/
            }
        }
    }
}

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 se almacena 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', usarlo 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 de esto es 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' de 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 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 colocar el código en un controlador de captura de 'Excepción', que romperá las excepciones no detectadas de FxCop y de su depurador.

Este es un problema clásico al que cada desarrollador de C # se enfrenta con el tiempo.

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

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

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

  1. Inicialice WebId al valor de reserva.
  2. Construye un nuevo Guid en una variable temporal.
  3. Establezca WebId en la variable temporal completamente construida. Haga que esta sea 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);
}

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

Entonces, tu 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 la duplicación de código.

Además, desde C # 6 tienes los exception-filters como ya se mencionó por otros. Para que pueda 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 utiliza un " catch-all " Excepción 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) que puede poner el tipo Excepción de captura en la parte inferior y esto capturará cualquier excepción que no haya sido mejorada 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 a la gente que revise este documento de MSDN:

Jerarquía de excepciones

¿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?

Por ejemplo:

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

// ...

private void DoSomething()
{
    // ...
}

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

Tenga en cuenta que encontré una manera 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;
    }
}

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

Me encontré con un caso de uso cuando intentaba convertir un objeto de control en un datagrid, 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
} 

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

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

Para esto necesitas crear varias " Captura " Sobrecargas de métodos, 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í sucesivamente tantos como desee. Pero debe hacerlo una vez y puede usarlo en todos sus proyectos (o, si creó un paquete nuget, también podríamos usarlo).

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

p.s. No he puesto cheques nulos para la simplicidad del código, considere agregar validaciones de parámetros.

p.s.2 Si desea devolver un valor desde el catch, es necesario hacer los mismos métodos de Catch, pero con retornos y Func en lugar de Action en los parámetros.

Simplemente llama al try y catch 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 simple!

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");
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top