tipos - try catch finally c# ejemplos




¿Atrapa múltiples excepciones a la vez? (18)

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

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

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

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

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

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

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

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

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

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

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


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


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

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

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

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

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

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

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

Puede descargar Visual Studio 2015 para verificar esto.

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

Install-Package Microsoft.Net.Compilers

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

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


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

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

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

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

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

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

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

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

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

Y úsalos así:

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

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


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

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

    WebId = Guid.Empty;
}

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

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

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

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

Qué tal si

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

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

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

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

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

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

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


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

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

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

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

    // Handle exception
}

Hay algunas ventajas de invertir la expresión:

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

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

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

    // Handle exception
}

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

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

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

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

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

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

ORIGINAL:

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

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

private void TestMethod ()
{
    Action<Exception> errorHandler = ( ex ) => {
        // write to a log, whatever...
    };

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Solo digo...

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

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

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

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

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

¿Puedo detectar múltiples excepciones a la vez?

En resumen, no.

Lo que lleva a la siguiente pregunta,

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

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

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

Entonces el código parece:

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

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

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

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

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

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

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

Uso

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

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

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

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }
    }
}

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.FluentValidation;

namespace IsAnyOfExceptionHandlerSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // High Level Error Handler (Log and Crash App)
            try
            {
                Foo();
            }
            catch (OutOfMemoryException ex)
            {
                Console.WriteLine("FATAL ERROR! System Crashing. " + ex.Message);
                Console.ReadKey();
            }
        }

        static void Foo()
        {
            // Init
            List<Action<string>> TestActions = new List<Action<string>>()
            {
                (key) => { throw new FormatException(); },
                (key) => { throw new ArgumentException(); },
                (key) => { throw new KeyNotFoundException();},
                (key) => { throw new OutOfMemoryException(); },
            };

            // Run
            foreach (var FooAction in TestActions)
            {
                // Mid-Level Error Handler (Appends Data for Log)
                try
                {
                    // Init
                    var SomeKeyPassedToFoo = "FooParam";

                    // Low-Level Handler (Handle/Log and Keep going)
                    try
                    {
                        FooAction(SomeKeyPassedToFoo);
                    }
                    catch (Exception ex)
                    {
                        if (ex.GetType().IsAnyOf(
                            typeof(FormatException),
                            typeof(ArgumentException)))
                        {
                            // Handle
                            Console.WriteLine("ex was {0}", ex.GetType().Name);
                            Console.ReadKey();
                        }
                        else
                        {
                            // Add some Debug info
                            ex.Data.Add("SomeKeyPassedToFoo", SomeKeyPassedToFoo.ToString());
                            throw;
                        }
                    }
                }
                catch (KeyNotFoundException ex)
                {
                    // Handle differently
                    Console.WriteLine(ex.Message);

                    int Count = 0;
                    if (!Validate.IsAnyNull(ex, ex.Data, ex.Data.Keys))
                        foreach (var Key in ex.Data.Keys)
                            Console.WriteLine(
                                "[{0}][\"{1}\" = {2}]",
                                Count, Key, ex.Data[Key]);

                    Console.ReadKey();
                }
            }
        }
    }
}

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

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }

        /// <summary>
        /// Validates if any passed in parameter is equal to null.
        /// </summary>
        /// <param name="p_parameters">Parameters to test for Null.</param>
        /// <returns>True if one or more parameters are null.</returns>
        public static bool IsAnyNull(params object[] p_parameters)
        {
            p_parameters
                .CannotBeNullOrEmpty("p_parameters");

            foreach (var item in p_parameters)
                if (item == null)
                    return true;

            return false;
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static void CannotBeNull(this object p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw
                    new
                        ArgumentNullException(
                        string.Format("Parameter \"{0}\" cannot be null.",
                        p_name), default(Exception));
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw new ArgumentNullException("Collection cannot be null.\r\nParameter_Name: " + p_name, default(Exception));

            if (p_parameter.Count <= 0)
                throw new ArgumentOutOfRangeException("Collection cannot be empty.\r\nParameter_Name: " + p_name, default(Exception));
        }

        /// <summary>
        /// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentException"></exception>
        public static void CannotBeNullOrEmpty(this string p_parameter, string p_name)
        {
            if (string.IsNullOrEmpty(p_parameter))
                throw new ArgumentException("String cannot be null or empty.\r\nParameter_Name: " + p_name, default(Exception));
        }
    }
}

Dos pruebas unitarias NUnit de muestra

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

using System;
using System.Collections.Generic;
using Common.FluentValidation;
using NUnit.Framework;

namespace UnitTests.Common.Fluent_Validations
{
    [TestFixture]
    public class IsAnyOf_Tests
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void IsAnyOf_ArgumentNullException_ShouldNotMatch_ArgumentException_Test()
        {
            Action TestMethod = () => { throw new ArgumentNullException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(ArgumentException), /*Note: ArgumentNullException derrived from ArgumentException*/
                    typeof(FormatException),
                    typeof(KeyNotFoundException)))
                {
                    // Handle expected Exceptions
                    return;
                }

                //else throw original
                throw;
            }
        }

        [Test, ExpectedException(typeof(OutOfMemoryException))]
        public void IsAnyOf_OutOfMemoryException_ShouldMatch_OutOfMemoryException_Test()
        {
            Action TestMethod = () => { throw new OutOfMemoryException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(OutOfMemoryException),
                    typeof(Exception)))
                    throw;

                /*else... Handle other exception types, typically by logging to file*/
            }
        }
    }
}

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

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

MyClass instance;
try { instance = ... }
catch(Exception1 e) { Reset(instance); }
catch(Exception2 e) { Reset(instance); }
catch(Exception) { throw; }

void Reset(MyClass instance) { /* reset the state of the instance */ }

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

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

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

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

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

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




exception-handling