web-dev-qa-db-fra.com

Attraper plusieurs exceptions à la fois?

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

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: y a-t-il un moyen d'attraper les deux exceptions et d'appeler uniquement l'appel WebId = Guid.Empty?

L'exemple donné est plutôt simple, puisqu'il ne s'agit que d'un GUID . Mais imaginez du code où vous modifiez un objet plusieurs fois, et si l’une des manipulations échoue de la manière attendue, vous souhaitez "réinitialiser" le object. Cependant, s'il y a une exception inattendue, je veux toujours la lancer plus haut.

1952
Michael Stum

Catch System.Exception et allumer les types

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

    throw;
}
1968
Joseph Daigle

EDIT: Je partage l'avis de ceux qui affirment qu'à partir de C # 6.0, les filtres d'exception sont désormais une solution parfaite: catch (Exception ex) when (ex is ... || ex is ... )

Sauf que je déteste toujours un peu la disposition d'une seule ligne et que j'établis personnellement le code comme suit. Je pense que cela est aussi fonctionnel qu'esthétique, car je pense 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 la fumée sacrée ...

Ce type de réponse duplique une réponse précédente, mais si vous voulez vraiment exécuter une action commune pour plusieurs types d'exceptions et garder le tout bien rangé dans le cadre d'une méthode, pourquoi ne pas utiliser simplement un lambda/fermeture/fonction en ligne pour faire quelque chose comme ce qui suit? Je veux dire, il y a de bonnes chances que vous finissiez par réaliser que vous voulez simplement faire de cette fermeture une méthode distincte que vous pouvez utiliser partout. Mais il sera alors très facile de le faire sans réellement modifier 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/de sarcasme à venir) pourquoi diable va-t-il faire tout cet effort pour simplement remplacer ce qui suit:

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

... avec une variation insensée de cette odeur de code suivante, je veux dire par exemple, uniquement pour prétendre que vous enregistrez quelques frappes au clavier.

// 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 automatiquement plus lisible.

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

Mais c'est un peu ce que je veux dire. Vous avez tous entendu parler de fonctions/méthodes, non? Sérieusement. Ecrivez une fonction ErrorHandler commune et, comme, appelez-la à partir 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 simultanément beaucoup plus sujet aux erreurs pendant la phase de maintenance de votre projet.

La phase de maintenance, pour ceux qui sont relativement nouveaux dans la programmation, représentera 98,7% ou plus de la durée de vie totale de votre projet, et le pauvre imbécile qui effectue la maintenance sera presque certainement quelqu'un d'autre que vous. Et il y a de fortes chances pour qu'ils consacrent 50% de leur temps au travail à maudire votre nom.

Et bien sûr, FxCop aboie et vous devez donc aussi ajouter un attribut à votre code qui a précisément à faire Zip avec le programme en cours, et n’est là que pour dire à FxCop d’ignorer un problème qui, dans 99,9% des cas, est parfaitement correct. Et, désolé, je peux me tromper, mais cet attribut "ignorer" n'est-il pas réellement compilé dans votre application?

Le fait de placer le test if complet sur une seule ligne le rendrait-il plus lisible? Je ne pense pas. Je veux dire, il y a longtemps, un autre programmeur a affirmé avec véhémence que mettre plus de code sur une ligne le rendrait "plus rapide". Mais bien sûr, il était complètement cinglé. Essayer de lui expliquer (avec un visage impassible - ce qui était difficile) comment l'interprète ou le compilateur séparerait cette longue ligne en déclarations discrètes d'une instruction par ligne - essentiellement identiques au résultat s'il était allé de l'avant et vient de rendre le code lisible au lieu d’essayer de démêler le compilateur - n’a aucun effet sur lui. Mais je m'égare.

Combien moins lisible cela obtient-il lorsque vous ajoutez trois types d'exception supplémentaires, dans un mois ou deux à partir de maintenant? (Réponse: cela devient beaucoup moins lisible).

En fait, l’un des principaux problèmes est que le but du formatage du code source textuel que nous examinons tous les jours est de rendre vraiment, vraiment visible pour les autres êtres humains, ce qui se passe réellement lorsque le code est exécuté. Parce que le compilateur transforme le code source en quelque chose de totalement différent et se moque de votre style de formatage. Donc, tout-en-un-ligne est totalement nul, aussi.

Juste en train de dire ...

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

Comme d'autres l'ont fait remarquer, vous pouvez avoir une instruction if dans votre bloc catch pour déterminer ce qui se passe. C # 6 prend en charge les filtres d’exception, de sorte que ce qui suit fonctionnera:

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

La méthode MyFilter pourrait alors ressembler à ceci:

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

Autrement, tout peut être fait en ligne (le côté droit de l'instruction when doit simplement être une expression booléenne).

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

Cela diffère de l'utilisation d'une instruction if à l'intérieur du bloc catch, l'utilisation de filtres d'exception ne déroulera pas la pile.

Vous pouvez télécharger Visual Studio 2015 pour vérifier cela.

Si vous souhaitez continuer à utiliser Visual Studio 2013, vous pouvez installer le package de nuget suivant:

Install-Package Microsoft.Net.Compilers

Au moment de la rédaction, cela inclura le support pour C # 6.

En référençant ce package, le projet sera généré à l'aide de la version spécifique des compilateurs C # et Visual Basic contenus dans le package, par opposition à toute version du système installé.

317
Joe

Pas en C # malheureusement, vous aurez besoin d’un filtre d’exception 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 en cas d’erreur, puis de l’appeler dans ces blocs de capture spécifiques:

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

Par souci d’exhaustivité, depuis . NET 4.0 le code peut être réécrit comme suit:

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

TryParse Ne lève jamais d'exception et renvoie false si le format est incorrect, définissant WebId sur Guid.Empty.


Depuis C # 7 , vous pouvez éviter d’introduire une variable sur une ligne distincte:

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

Vous pouvez également créer des méthodes d'analyse de tuples renvoyés, qui n'étaient pas encore disponibles dans .NET Framework à partir de la version 4.6:

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

Et utilisez-les comme ceci:

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

La prochaine mise à jour inutile de cette réponse inutile survient lorsque la déconstruction des paramètres sortants est implémentée en C # 12. :)

129
Athari

Si vous pouvez mettre à niveau votre application vers C # 6, vous avez de la chance. La nouvelle version C # a implémenté les filtres d’exception. Vous pouvez donc é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 réalité, il s'agit de la seule nouvelle fonctionnalité de C # 6 qu'il n'est pas possible d'émuler dans les versions précédentes. Premièrement, un re-lancer signifie plus de frais généraux que de sauter la prise. Deuxièmement, ce n’est pas sémantiquement équivalent. La nouvelle fonctionnalité conserve la pile intacte lorsque vous déboguez votre code. Sans cette fonctionnalité, le vidage sur incident est moins utile, voire inutile.

Voir un discussion à ce sujet sur CodePlex . Et un exemple montrant la différence .

72
Maniero

Les filtres d'exception sont maintenant disponibles dans c # 6+. Tu peux faire

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

En C # 7.0+, vous pouvez également combiner ceci avec la correspondance de motifs.

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..? 
}
70
Mat J

Si vous ne souhaitez pas utiliser une instruction if dans les étendues catch, dans _C# 6.0_, vous pouvez utiliser _Exception Filters_ syntaxe qui était déjà prise en charge par le CLR dans les versions d'aperçu mais n'existait que dans _VB.NET_/MSIL:

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

Ce code n'accroche la Exception que s'il s'agit d'un InvalidDataException ou ArgumentNullException.

En fait, vous pouvez mettre n'importe quelle condition dans cette clause when:

_static int a = 8;

...

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

Notez que contrairement à une instruction if à l'intérieur de la portée de catch, _Exception Filters_ ne peut pas lancer Exceptions, et quand ils le font ou lorsque la condition n'est pas true , la prochaine condition catch sera évaluée à la place:

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

Sortie: capture générale.

Lorsqu'il y a plus d'un true _Exception Filter_ - le premier sera accepté:

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

Sortie: capture.

Et comme vous pouvez le voir dans la MSIL, le code n'est pas traduit en instructions if, mais en Filters, et Exceptions ne peut être jeté de l'intérieur des zones marquées d'un _Filter 1_ et _Filter 2_ mais le filtre rejetant le Exception échouera, mais la dernière valeur de comparaison transmise à la pile avant la commande endfilter déterminera le succès/l'échec du filtre ( _Catch 1_ XOR _Catch 2_ s'exécutera en conséquence):

Exception Filters MSIL

De plus, spécifiquement Guid a la méthode Guid.TryParse .

31
Tamir Vered

Avec C # 7 la réponse de Michael Stum peut être amélioré tout en conservant la lisibilité d'une instruction switch:

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

en C # 6, l’approche recommandée consiste à utiliser des filtres d’exception, voici un exemple:

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

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

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

CA1800: ne jetez pas inutilement le casting indique à "envisager de tester le résultat de l'opérateur 'en tant que" ", mais si vous Faites cela, vous écrivez plus de code que si vous attrapiez chaque exception séparément.

Quoi qu'il en soit, voici ce que je ferais:

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}
18
Matt

Ceci est une variante de la réponse de Matt (je pense que c'est un peu plus propre) ... utilisez une méthode:

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

    WebId = Guid.Empty;
}

Toute autre exception sera levée et le code WebId = Guid.Empty; ne sera pas touché. Si vous ne voulez pas que d'autres exceptions provoquent un crash de votre programme, ajoutez simplement ceci APRES les deux autres captures:

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

Réponse de Joseph Daigle est une bonne solution, mais j’ai trouvé que la structure suivante était un peu plus ordonnée et moins sujette aux erreurs.

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

    // Handle exception
}

L'inversion de l'expression présente quelques avantages:

  • 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 déclarations de 'lancer' ou de 'retour' qui, dans la solution de Joseph, sont séparées 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 en C # 6.0 rendra la syntaxe un peu plus propre et sera accompagné d'un nombre de autres avantages par rapport à toute solution actuelle. (notamment en laissant la pile indemne)

Voici à quoi ressemblerait le même problème avec la syntaxe C # 6.0:

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

@ Micheal

Version légèrement révisé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.

16
FlySwat

Que diriez-vous

try
{
    WebId = Guid.Empty;
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
13
Maurice
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}
12
Konstantin Spirin

Attentioned and Warned: Encore un autre genre, style fonctionnel.

Le contenu du lien ne répond pas directement à votre question, mais il est trivial de l’étendre de la manière suivante:

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

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

(Fournit essentiellement une autre surcharge Catch vide qui se retourne elle-même)

La plus grande question à ceci est pourquoi . Je ne pense pas que le coût dépasse le gain ici :)

11
nawfal

Mise à jour 2015-12-15: voir https://stackoverflow.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 intercepter une seule fois et filtrer les exceptions, j’utilise une méthode d’extension comme indiqué ci-dessous.

J'avais déjà cette extension dans ma bibliothèque, écrite à l'origine pour d'autres raisons, mais elle fonctionnait parfaitement pour la vérification de type sur les exceptions. De plus, à mon humble avis, cela semble plus propre qu’un groupe d’énoncés ||. De plus, contrairement à la réponse acceptée, je préfère la gestion explicite des exceptions afin que ex is ... ait un comportement indésirable, car les classes dérivées sont assignables à leurs types parents).

Utilisation

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

Extension IsAnyOf.cs (voir l'exemple de traitement d'erreur complet 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 de traitement d'erreur complet (copier-coller dans 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));
        }
    }
}

Deux exemples de tests unitaires NUnit

Le comportement de correspondance pour les types Exception est exact (c'est-à-dire, un enfant IS PAS une correspondance pour aucun de ses types parents).

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

Comme j'avais l'impression que ces réponses venaient de toucher la surface, j'ai essayé de creuser un peu plus profondément.

Donc, ce que nous voulons vraiment faire est quelque chose qui ne compile pas, disons:

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

Si nous voulons cela, c'est parce que nous ne voulons pas que le gestionnaire d'exceptions récupère les éléments dont nous avons besoin plus tard dans le processus. Bien sûr, nous pouvons attraper une exception et vérifier avec un "si" quoi faire, mais soyons honnêtes, nous ne voulons pas vraiment cela. (FxCop, problèmes de débogueur, laideur)

Alors, pourquoi ce code ne compile-t-il pas - et comment pouvons-nous le pirater de manière à le faire?

Si nous examinons le code, ce que nous aimerions vraiment faire, c'est transférer l'appel. Cependant, selon MS Partition II, les blocs de gestionnaire d'exceptions IL ne fonctionneront pas de la sorte, ce qui est logique dans le cas présent car cela impliquerait que l'objet "exception" puisse avoir différents types.

Ou pour l'écrire en code, nous demandons au compilateur de faire quelque chose comme ça (enfin ce n'est pas tout à fait correct, mais c'est la chose la plus proche possible, je suppose):

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

La raison pour laquelle cela ne compilera pas est assez évidente: quel type et quelle valeur aurait l'objet '$ exception' (qui sont ici stockés dans les variables 'e')? Nous voulons que le compilateur gère cela: il faut noter que le type de base commun des deux exceptions est 'Exception', utilisez-le pour qu'une variable contienne les deux exceptions, puis ne gérez que les deux exceptions interceptées. La manière dont cela est implémenté dans IL est sous la forme d'un "filtre", disponible dans VB.Net.

Pour que cela fonctionne en C #, nous avons besoin d'une variable temporaire avec le type de base "Exception" correct. Pour contrôler le flux du code, nous pouvons ajouter des branches. Voici:

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

Les inconvénients évidents pour cela sont que nous ne pouvons pas re-lancer correctement et, eh bien, soyons honnêtes, c'est une très mauvaise solution. La laideur peut être un peu corrigée en éliminant les branches, ce qui améliore légèrement la solution:

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

Cela laisse juste le 're-lancer'. Pour que cela fonctionne, nous devons être en mesure d'effectuer la manipulation à l'intérieur du bloc "catch" - et la seule façon de le faire est d'utiliser un objet "Exception" qui capture.

À ce stade, nous pouvons ajouter une fonction distincte qui gère les différents types d’exceptions à l’aide de la résolution de surcharge, ou pour gérer l’exception. Les deux ont des inconvénients. Pour commencer, voici le moyen de le faire avec une fonction d'assistance:

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

Et l'autre solution consiste à attraper l'objet Exception et à le gérer en conséquence. La traduction la plus littérale pour cela, basée sur le contexte ci-dessus est la suivante:

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

Donc pour conclure:

  • Si nous ne voulons pas relancer, nous pourrions envisager de capturer les bonnes exceptions et de les stocker dans un fichier temporaire.
  • Si le gestionnaire est simple et que nous souhaitons réutiliser du code, la meilleure solution consiste probablement à introduire une fonction d'assistance.
  • Si nous voulons relancer, nous n'avons pas d'autre choix que de placer le code dans un gestionnaire de captures 'Exception', ce qui cassera FxCop et les exceptions non capturées de votre débogueur.
7
atlaste

C’est un problème classique auquel tous les développeurs C # sont finalement confrontés.

Permettez-moi de diviser votre question en 2 questions. La première,

Puis-je intercepter plusieurs exceptions à la fois?

Bref non.

Ce qui mène à la question suivante,

Comment éviter d'écrire du code en double étant donné que je ne peux pas attraper plusieurs types d'exceptions dans le même bloc catch ()?

Étant donné votre échantillon spécifique, où la valeur de remplacement est peu coûteuse à construire, j'aime suivre ces étapes:

  1. Initialisez WebId sur la valeur de repli.
  2. Construire un nouveau Guid dans une variable temporaire.
  3. Définissez WebId sur la variable temporaire entièrement construite. Faites-en la déclaration finale du bloc try {}.

Le code ressemble donc à:

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 une exception est levée, WebId n'est jamais défini sur la valeur semi-construite et reste Guid.Empty.

Si la construction de la valeur de repli est coûteuse et que la réinitialisation d'une valeur coûte beaucoup moins cher, alors je déplacerais le code de réinitialisation dans sa propre fonction:

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

Vous répétez donc beaucoup de code dans chaque commutateur d’exception? On dirait que l'extraction d'une méthode serait une bonne idée, n'est-ce pas?

Donc, votre code se résume à ceci:

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

Je me demande pourquoi personne n'a remarqué cette duplication de code.

A partir de C # 6, vous avez en outre les filtres d'exception déjà mentionnés par d'autres. Vous pouvez donc modifier le code ci-dessus en ceci:

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

Essayez peut-être de garder votre code simple, par exemple en plaçant le code commun dans une méthode, comme vous le feriez dans une autre partie du code qui ne se trouve pas dans une clause catch?

Par exemple.:

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

// ...

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

Juste comment je le ferais, en essayant de trouver le modèle simple est bea

4
Żubrówka

Je voulais ajouter ma réponse courte à ce fil déjà long. Un élément qui n’a pas été mentionné est l’ordre de priorité des instructions catch. Plus précisément, vous devez connaître la portée de chaque type d’exception que vous essayez d’attraper.

Par exemple, si vous utilisez une exception "catch-all" en tant qu'exception , elle précédera toutes les autres instructions catch et vous obtiendrez évidemment des erreurs de compilation, toutefois, si vous inversez Pour que vous puissiez enchaîner vos instructions de capture (un peu d’un anti-motif, je pense), vous pouvez mettre le type général Exception en bas et il s'agira de capturer toutes les exceptions non prises en compte dans votre bloc 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);
            }

Je recommande vivement aux personnes de consulter ce document MSDN:

Hiérarchie des exceptions

4
Tahir Khalid

Notez que j'ai trouvé un moyen de le faire, mais cela ressemble plus à du matériel pour The Daily WTF :

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

Il convient de mentionner ici. Vous pouvez répondre aux combinaisons multiples (erreur d'exception et exception.message).

J'ai rencontré un scénario de cas d'utilisation lorsque j'essayais de transtyper un objet de contrôle dans une grille de données, avec un contenu tel que TextBox, TextBlock ou CheckBox. Dans ce cas, l'exception renvoyée était la même, mais le message variait.

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
} 
0
George

Je veux suggérer la réponse la plus courte (encore une style fonctionnel):

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

Pour cela, vous devez créer plusieurs surcharges de méthode "Catch", similaires à 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));
    }

et ainsi de suite autant que vous le souhaitez. Mais vous devez le faire une fois et vous pouvez l’utiliser dans tous vos projets (ou, si vous avez créé un paquet de nugets, nous pourrions également l’utiliser).

Et la mise en œuvre 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. Je n'ai pas mis de contrôle nul pour la simplicité du code, pensez à ajouter des validations de paramètres.

p.s.2 Si vous voulez renvoyer une valeur à partir du catch, il est nécessaire de faire les mêmes méthodes Catch, mais avec des retours et Func au lieu de Action dans les paramètres.

0
Eugene Gorbovoy