[c#] Attraper plusieurs exceptions à la fois?


Answers

EDIT: Je suis d'accord avec les autres qui disent que, à partir de C # 6.0, les filtres d'exception sont maintenant un très bon moyen d'aller: catch (Exception ex) when (ex is ... || ex is ... )

Sauf que je déteste encore un peu la mise en page d'une ligne longue et je poserais personnellement le code comme suit. Je pense que c'est aussi fonctionnel qu'esthétique, car je crois que cela améliore la compréhension. Certains peuvent être en désaccord:

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

ORIGINAL:

Je sais que je suis un peu en retard à la fête ici, mais de la fumée sacrée ...

Couper directement à la chasse, ce genre de dupliquer une réponse plus tôt, mais si vous voulez vraiment effectuer une action commune pour plusieurs types d'exception et garder le tout net et bien rangé dans le cadre de la méthode unique, pourquoi ne pas utiliser un lambda / fermeture / fonction inline pour faire quelque chose comme ce qui suit? Je veux dire, les chances sont assez bonnes que vous finirez par réaliser que vous voulez juste faire de cette fermeture une méthode séparée que vous pouvez utiliser partout. Mais alors ce sera super facile de le faire sans réellement changer le reste du code structurellement. Droite?

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

Je ne peux pas m'empêcher de me demander ( avertissement: un peu d'ironie / sarcasme à venir) pourquoi diable aller à tous ces efforts pour remplacer simplement ce qui suit:

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

... avec une variation folle de cette prochaine odeur de code, je veux dire par exemple, seulement pour prétendre que vous enregistrez quelques frappes.

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

Parce que ce n'est certainement pas plus lisible automatiquement.

Certes, j'ai laissé les trois instances identiques de /* write to a log, whatever... */ return; sur le premier exemple.

Mais c'est en quelque sorte mon point de vue. Vous avez entendu parler des fonctions / méthodes, n'est-ce pas? Sérieusement. Écrivez une fonction ErrorHandler commune et, comme, appelez-la de chaque bloc catch.

Si vous me le demandez, le deuxième exemple (avec les mots clés if et is ) est à la fois nettement moins lisible et nettement plus sujet aux erreurs pendant la phase de maintenance de votre projet.

La phase de maintenance, pour quiconque est relativement nouveau dans la programmation, représentera 98,7% ou plus de la durée de vie globale de votre projet, et le pauvre schmuck qui s'occupe de la maintenance sera presque certainement quelqu'un d'autre que vous. Et il y a une très bonne chance qu'ils passent 50% de leur temps au travail en maudissant votre nom.

Et bien sûr, FxCop aboie chez vous et vous devez donc également ajouter un attribut à votre code qui a précisément un lien avec le programme en cours, et est seulement là pour dire à FxCop d'ignorer un problème qui dans 99.9% des cas est totalement corriger en signalant. Et, désolé, je peux me tromper, mais cet attribut "ignorer" n'est-il pas réellement compilé dans votre application?

Est-ce que mettre tout le test if sur une ligne le rendrait plus lisible? Je ne pense pas. Je veux dire, j'ai eu un autre programmeur véhémentement argumenter il y a longtemps que mettre plus de code sur une ligne le ferait «courir plus vite». Mais bien sûr, il était complètement fou. En essayant de lui expliquer (avec un visage impassible - ce qui était difficile) comment l'interprète ou le compilateur diviserait cette longue ligne en phrases discrètes d'une instruction par ligne - essentiellement identique au résultat s'il était parti devant et juste fait le code lisible au lieu d'essayer de surpasser le compilateur - n'a eu aucun effet sur lui que ce soit. Mais je m'égare.

À quel point est-ce moins lisible lorsque vous ajoutez trois types d'exception, un mois ou deux à partir de maintenant? (Réponse: il devient beaucoup moins lisible).

L'un des points importants, en fait, c'est que la plupart du formatage du code source textuel que nous regardons tous les jours est de rendre vraiment, vraiment évident pour les autres êtres humains ce qui se passe réellement lorsque le code s'exécute. Parce que le compilateur transforme le code source en quelque chose de totalement différent et ne se soucie pas du style de mise en forme de votre code. Donc tout sur une seule ligne est vraiment nul.

Je dis juste ...

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

Il est déconseillé d'attraper simplement System.Exception . Au lieu de cela, seules les exceptions "connues" doivent être interceptées.

Maintenant, cela conduit parfois à un code répétitif inutile, par exemple:

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

Je me demande: existe-t-il un moyen d'intercepter les deux exceptions et de n'appeler l'appel WebId = Guid.Empty qu'une seule fois?

L'exemple donné est plutôt simple, car c'est seulement un GUID . Mais imaginez un code dans lequel vous modifiez un objet plusieurs fois, et si l'une des manipulations échoue de la manière prévue, vous voulez "réinitialiser" l' object . Cependant, s'il y a une exception inattendue, je veux quand même l'augmenter.




@Micheal

Version légèrement modifiée de votre code:

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

Les comparaisons de chaînes sont laides et lentes.




Si vous pouvez mettre à jour votre application en C # 6, vous avez de la chance. La nouvelle version C # a implémenté des filtres Exception. Donc vous pouvez écrire ceci:

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

Certaines personnes pensent que ce code est le même que

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

Mais ce n'est pas. En fait c'est la seule nouvelle fonctionnalité de C # 6 qu'il n'est pas possible d'émuler dans les versions précédentes. Tout d'abord, un nouveau lancer signifie plus de frais généraux que de sauter la capture. Deuxièmement, ce n'est pas sémantiquement équivalent. La nouvelle fonctionnalité préserve la pile intacte lorsque vous déboguez votre code. Sans cette fonctionnalité, le vidage sur incident est moins utile ou même inutile.

Voir une roslyn.codeplex.com/discussions/541301 . Et un exemple montrant la différence .




Just call the try and catch twice.

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

It is just that Simple!!




La réponse acceptée semble acceptable, sauf que CodeAnalysis / FxCop se plaindra du fait qu'il attrape un type d'exception générale.

En outre, il semble que l'opérateur "est" peut dégrader légèrement les performances.

CA1800: Ne lancez pas inutilement la commande "envisager de tester le résultat de l'opérateur 'as'", mais si vous faites cela, vous écrirez plus de code que si vous attrapiez chaque exception séparément.

De toute façon, voici ce que je ferais:

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}



So you´re repeating lots of code within every exception-switch? Sounds like extracting a method would be god idea, doesn´t it?

So your code comes down to this:

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 */ }

I wonder why no-one noticed that code-duplication.

From C#6 you furthermore have the exception-filters as already mentioned by others. So you can modify the code above to this:

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



Pas en C # malheureusement, car vous auriez besoin d'un filtre d'exception pour le faire et C # n'expose pas cette fonctionnalité de MSIL. VB.NET a cependant cette capacité, par exemple

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

Ce que vous pouvez faire est d'utiliser une fonction anonyme pour encapsuler votre code sur erreur, puis l'appeler dans ces blocs catch spécifiques:

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



La réponse de Joseph Daigle est une bonne solution, mais j'ai trouvé la structure suivante pour être un peu plus ordonnée et moins sujet aux erreurs.

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

    // Handle exception
}

Il y a quelques avantages à inverser l'expression:

  • Une déclaration de retour n'est pas nécessaire
  • Le code n'est pas imbriqué
  • Il n'y a aucun risque d'oublier les énoncés «jeter» ou «retourner» qui, dans la solution de Joseph, sont séparés de l'expression.

Il peut même être compacté en une seule ligne (mais pas très jolie)

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

    // Handle exception
}

Edit: Le filtrage des exceptions dans C # 6.0 rendra la syntaxe un peu plus propre et s'accompagnera d'un certain nombre d'autres avantages par rapport à toute solution actuelle. (en laissant notamment la pile intacte)

Voici comment le même problème apparaîtrait en utilisant la syntaxe C # 6.0:

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



Mise à jour 2015-12-15: Voir https://.com/a/22864936/1718702 pour C # 6. C'est un nettoyeur et maintenant standard dans la langue.

Destiné aux personnes qui veulent une solution plus élégante pour attraper une fois et filtrer les exceptions, j'utilise une méthode d'extension comme démontré ci-dessous.

J'avais déjà cette extension dans ma bibliothèque, écrite à l'origine à d'autres fins, mais cela fonctionnait parfaitement pour la vérification des exceptions. De plus, à mon humble avis, il semble plus propre qu'un tas de || déclarations. De plus, contrairement à la réponse acceptée, je préfère la gestion explicite des exceptions, donc ex is ... a un comportement indésirable puisque les classes dérivées sont assignables à leurs types parents.

Usage

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

IsAnyOf.cs Extension (Voir l'exemple complet de gestion des erreurs pour les dépendances)

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

Exemple complet de gestion des erreurs (Copier-coller vers une nouvelle application de console)

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

Two Sample NUnit Unit Tests

Matching behaviour for Exception types is exact (ie. A child IS NOT a match for any of its parent types).

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*/
            }
        }
    }
}



This is a classic problem every C# developer faces eventually.

Let me break your question into 2 questions. The first,

Can I catch multiple exceptions at once?

En bref, non.

Which leads to the next question,

How do I avoid writing duplicate code given that I can't catch multiple exception types in the same catch() block?

Given your specific sample, where the fall-back value is cheap to construct, I like to follow these steps:

  1. Initialize WebId to the fall-back value.
  2. Construct a new Guid in a temporary variable.
  3. Set WebId to the fully constructed temporary variable. Make this the final statement of the try{} block.

So the code looks like:

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) {}

If any exception is thrown, then WebId is never set to the half-constructed value, and remains Guid.Empty.

If constructing the fall-back value is expensive, and resetting a value is much cheaper, then I would move the reset code into its own function:

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



Wanted to added my short answer to this already long thread. Something that hasn't been mentioned is the order of precedence of the catch statements, more specifically you need to be aware of the scope of each type of exception you are trying to catch.

For example if you use a "catch-all" exception as Exception it will preceed all other catch statements and you will obviously get compiler errors however if you reverse the order you can chain up your catch statements (bit of an anti-pattern I think) you can put the catch-all Exception type at the bottom and this will be capture any exceptions that didn't cater for higher up in your try..catch block:

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

I highly recommend folks review this MSDN document:

Exception Hierarchy




Mise en garde et averti: Encore un autre type, style fonctionnel.

Ce qui est dans le lien ne répond pas directement à votre question, mais il est trivial de l'étendre pour ressembler à:

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

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

(Fournissez fondamentalement une autre surcharge de Catch vide qui se retourne)

La plus grande question à ceci est pourquoi . Je ne pense pas que le coût l'emporte sur le gain ici :)




Related