Question

Il est déconseillé d’attraper simplement System.Exception . Au lieu de cela, seuls les éléments "connus" des exceptions doivent être intercepté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: existe-t-il un moyen d'attraper les deux exceptions et d'appeler l'appel WebId = Guid.Empty une seule fois?

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". l'objet . Cependant, s’il existe une exception inattendue, je souhaite tout de même la lancer plus haut.

Était-ce utile?

La solution

Catch System.Exception et activer les types

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

    throw;
}

Autres conseils

MODIFIER: Je partage l'avis de ceux qui affirment que, à partir de C # 6.0, les filtres d'exception constituent désormais un moyen tout à fait judicieux: catch (Exception ex) lorsque (ex est ... || ex is ...)

Sauf que je déteste toujours la mise en page avec 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 ne pas être d'accord:

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

Pour aller droit au but, ce genre de doublons une réponse précédente, mais si vous voulez vraiment exécuter une action commune pour plusieurs types d'exception et garder le tout bien rangé dans le cadre d'une méthode, pourquoi ne pas simplement utiliser une fonction lambda / fermeture / 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. Droit?

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 m'empêcher de me demander ( avertissement: un peu d'ironie / de sarcasme à venir), pourquoi diable va-t-il au-delà de 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, par exemple, 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 / * écrire dans un journal, quel que soit ... * / return; du 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 appelez-la à partir de chaque bloc catch.

Si vous me le demandez, le deuxième exemple (avec les mots clés si et est ) 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 novices en programmation, représentera 98,7% ou plus de la durée 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 vous aboie et vous devez donc également ajouter un attribut à votre code, qui a précisément pour objectif le programme en cours, FxCop doit ignorer un problème qui, dans 99,9% des cas, est parfaitement correct. Et, désolé, je peux me tromper, mais est-ce que "ignorer" attribut finissent-ils réellement compilés dans votre application?

Le fait de mettre l'ensemble du code si 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 à exécuter. 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 de moins en moins cela devient-il lisible lorsque vous ajoutez trois autres types d'exceptions, dans un mois ou deux? (Réponse: cela devient beaucoup moins lisible).

En fait, l’un des principaux problèmes est que l’objet principal 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 s'exécute. 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 suce totalement aussi.

Je viens de dire ...

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

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. Ainsi, les éléments suivants fonctionneront:

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

Alternativement, cela 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 si à l'intérieur du bloc catch , l'utilisation de filtres d'exception ne ne déroulera pas la pile.

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

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

  

Paquet d'installation Microsoft.Net.Compilers

Au moment de la rédaction de cet article, cela inclura la prise en charge de C # 6.

  

En référençant ce paquet, le projet sera construit en utilisant le   version spécifique des compilateurs C # et Visual Basic contenus dans le   package, par opposition à toute version installée du système.

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

Vous pouvez utiliser une fonction anonyme pour encapsuler votre code en cas d'erreur, puis l'appeler dans ces blocs de capture spécifiques:

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

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éfinissez 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 nuplets retournés, qui n'étaient pas encore disponibles dans .NET Framework à compter 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.:)

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 identique à

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

Mais ce n'est pas le cas. 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 la discussion à ce sujet sur CodePlex . Et un exemple montrant la différence .

Les filtres d’exception sont maintenant disponibles dans c # 6+. Vous pouvez 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 cela avec la correspondance de motif

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

Si vous ne souhaitez pas utiliser une instruction si dans les portées catch , dans C # 6.0 , vous pouvez utiliser la syntaxe Filtres d'exception qui était déjà prise en charge par le CLR dans les versions de prévisualisation 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 interceptera Exception uniquement s'il s'agit d'une 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 si dans la portée de catch , Filtres d'exception 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 Filtre d'exception - 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 MSIL , le code n'est pas traduit en instructions si , mais en Filtres et Exceptions. ne peut pas être rejeté depuis les zones marquées par Filtre 1 et Filtre 2 , mais le filtre renvoyant l’exception Exception échouera également. la dernière valeur de comparaison placée dans la pile avant la commande endfilter déterminera le succès / l'échec du filtre ( Catch 1 XOR Catch 2 sera exécuté en conséquence):

 Filtres d'exception MSIL

De même, Guid contient spécifiquement le Méthode Guid.TryParse .

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

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

CA1800: ne lance pas de casting inutilement dit à "envisager de tester le résultat de l'opérateur" en tant que ", mais si vous le faites, vous écrivez plus de code que si vous interceptiez 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
}

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

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

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

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 bloquent votre programme, ajoutez 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
}

La réponse de Joseph Daigle est une bonne solution, mais j'ai trouvé la structure suivante doit être un peu plus ordonnée et moins sujette 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'oubli des déclarations de type "lancer" ou "retour" qui, dans la solution de Joseph, sont séparées de l'expression.

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

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

    // Handle exception
}

Modifier: filtrage d'exceptions en C # 6.0 La syntaxe sera un peu plus propre et sera fournie avec un nombre d'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
}

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

Que diriez-vous de

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

Averti et averti: Encore un autre type , 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... })(); 
}

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

La principale question qui se pose à cette question est pourquoi . Je ne pense pas que le coût l'emporte sur le gain ici:)

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

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 souhaitent une solution plus élégante pour intercepter une seule fois et filtrer les exceptions, j'utilise une méthode d'extension, comme illustré. 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 avis, cela semble plus propre qu'un tas d'instructions || . De plus, contrairement à la réponse acceptée, je préfère la gestion des exceptions explicites afin que ex soit ... 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 Exemple de gestion d'erreur complète 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 gestion d'erreur complète (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 des types Exception est exact (un enfant N’EST 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*/
            }
        }
    }
}

Puisque 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 voudrions 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 telle manière qu'il le fasse?

Si nous examinons le code, nous aimerions vraiment faire suivre 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 pour 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. Ceci est implémenté dans IL comme 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. Voilà:

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

L’inconvénient évident en est que nous ne pouvons pas re-lancer correctement, et bien, soyons honnêtes, c’est la 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 faire fonctionner ce travail consiste à capturer un objet "Exception".

À 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 de la gestion de 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.");

L’autre solution consiste à récupérer 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 souhaitons pas effectuer une nouvelle tentative, 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 le 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.

C’est un problème classique auquel tout développeur C # est confronté à terme.

Permettez-moi de diviser votre question en 2 questions. Le premier,

Puis-je intercepter plusieurs exceptions à la fois?

En bref, non.

Ce qui mène à la question suivante,

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

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

  1. Initialisez WebId à la valeur de repli.
  2. Construisez 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 secours 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);
}

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?

Votre code revient donc à 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.

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

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 un "fourre-tout", En tant qu'exception Exception , il précédera toutes les autres instructions catch et vous obtiendrez évidemment des erreurs de compilation. Toutefois, si vous inversez l'ordre, vous pouvez enchaîner vos instructions catch (un peu d'un anti-motif je pense) que vous pouvez mettre le type Exception fourre-tout situé au bas de la page et capturera toutes les exceptions non traitées plus haut 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 utilisateurs de consulter ce document MSDN:

Hiérarchie des exceptions

Peut-être essayez-vous 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?

Exemple:

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

// ...

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

Comme je le ferais, essayer de trouver le modèle simple est magnifique

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

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

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, dont le contenu était 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
} 

Je souhaite suggérer la réponse la plus courte (encore un style fonctionnel ):

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

Pour cela, vous devez créer plusieurs "Catch". surcharges de méthode, 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 l'implémentation 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, envisagez d'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 return et Func au lieu de Action dans les paramètres.

Appelez simplement essayer et attraper deux fois.

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

C’est aussi simple que ça!

Dans c # 6.0, les filtres d’exception sont des améliorations apportées à la gestion des exceptions

.
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");
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top