Attraper plusieurs exceptions à la fois?


2140

Il est déconseillé de simplement attraper 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'attraper les deux exceptions et de ne l' WebId = Guid.Emptyappeler qu'une seule fois?

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


5
Si vous utilisez .net 4 et supérieur, je préfère utiliser l'agrégationexception msdn.microsoft.com/en-us/library/system.aggregateexception.aspx
Bepenfriends

2
Bepenfriends - Puisque System.Guid ne lance pas AggregateException , ce serait bien si vous (ou quelqu'un) pouviez poster une réponse montrant comment vous l'envelopperiez dans une AggregateException etc.
weir

1
Sur l'utilisation AggregateException: Lancer une AggregateException dans mon propre code
DavidRR

11
"Il est déconseillé de simplement attraper System.Exception." -et si la méthode peut lever 32 types d'exceptions, que fait-on? écrire des captures pour chacun d'eux séparément?
giorgim

5
Si une méthode lève 32 types d'exceptions différents, c'est mal écrit. Soit il n'attrape pas les exceptions que ses propres appels font, il fait trop loin dans une méthode, ou la majorité / la totalité de ces 32 devrait être une seule exception avec un code de raison.
Flynn1179

Réponses:


2100

Attrapez System.Exceptionet allumez les types

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

    throw;
}

69
Malheureusement, FxCop (c'est-à-dire - Visual Studio Code Analysis) ne l'aime pas lorsque vous interceptez l'exception.
Andrew Garrison

15
Je suis d'accord pour ne pas attraper l'exception, mais, dans ce cas, la capture est un filtre. Vous pouvez avoir une couche supérieure qui gérera d'autres types d'exceptions. Je dirais que c'est correct, même si cela inclut un catch (Exception x). Il ne modifie pas le flux du programme, il gère simplement certaines exceptions, puis laisse le reste de l'application gérer tout autre type d'exception.
lkg

28
La dernière version de FxCop ne lève pas d'exception lorsque le code ci-dessus est utilisé.
Peter

28
Je ne sais pas ce qui n'allait pas avec le code OP en premier lieu. La réponse acceptée # 1 est presque deux fois plus de lignes et beaucoup moins lisible.
João Bragança

22
@ JoãoBragança: Bien que cette réponse dans cet exemple utilise plus de lignes, essayez d'imaginer si vous traitez par exemple les E / S de fichiers, et tout ce que vous voulez faire est d'attraper ces exceptions et de faire de la messagerie, mais uniquement celles que vous attendez de votre part. fichier méthodes IO. Ensuite, vous devez souvent faire face à un plus grand nombre (environ 5 ou plus) différents types d'exceptions. Dans cette situation, cette approche peut vous faire économiser quelques lignes.
Xilconic

595

EDIT: Je suis d'accord avec d'autres qui disent que, depuis C # 6.0, les filtres d'exception sont désormais une solution parfaitement adaptée:catch (Exception ex) when (ex is ... || ex is ... )

Sauf que je déteste toujours la mise en page sur une seule ligne et que je présenterais personnellement le code comme suit. Je pense que c'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 sainte fumée ...

Aller droit au but, ce type de duplique une réponse antérieure, mais si vous voulez vraiment effectuer une action commune pour plusieurs types d'exceptions et garder le tout propre et bien rangé dans le cadre de la seule méthode, pourquoi ne pas simplement utiliser un lambda / fermeture / fonction en ligne pour faire quelque chose comme ce qui suit? Je veux dire, les chances sont assez bonnes que vous finissiez par réaliser que vous voulez simplement faire de cette fermeture une méthode distincte que vous pouvez utiliser partout. Mais alors il 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 faire 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 odeur de code suivante, je veux dire par exemple, seulement pour prétendre que vous enregistrez quelques touches.

// 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 qu'il n'est certainement pas automatiquement plus lisible.

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

Mais c'est en quelque sorte mon point. Vous avez tous entendu parler des fonctions / méthodes, non? Sérieusement. Écrivez une ErrorHandlerfonction commune et, comme, appelez-la depuis chaque bloc catch.

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

La phase de maintenance, pour toute personne qui pourrait être relativement nouvelle dans la programmation, représentera 98,7% ou plus de la durée de vie globale de votre projet, et le pauvre schmuck qui effectue la maintenance va presque certainement être quelqu'un d'autre que vous. Et il y a de fortes chances qu'ils passent 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 un lien avec le programme en cours d'exécution, et n'est là que pour dire à FxCop d'ignorer un problème qui, dans 99,9% des cas, est totalement correct en signalant. Et, désolé, je peux me tromper, mais cet attribut "ignorer" ne se compile-t-il pas réellement dans votre application?

Est-ce que mettre l'ensemble du iftest sur une seule ligne le rendrait plus lisible? Je ne pense pas. Je veux dire, il y a longtemps, un autre programmeur a soutenu avec véhémence que mettre plus de code sur une ligne le rendrait "plus rapide". Mais bien sûr, il était complètement fou. Essayer de lui expliquer (avec un visage impassible - ce qui était difficile) comment l'interprète ou le compilateur briserait 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 surpasser le compilateur - n'a eu aucun effet sur lui. Mais je m'égare.

À quel point cela est-il moins lisible lorsque vous ajoutez trois autres types d'exception, dans un mois ou deux? (Réponse: ça devient beaucoup moins lisible).

L'un des points majeurs, en réalité, est que la plupart du point de formatage du code source textuel que nous regardons tous 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 formatage de votre code. Donc, tout-en-une-ligne est tout aussi 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;
}

36
Quand je suis tombé sur cette question pour la première fois, j'étais sur la réponse acceptée. Cool, je peux juste attraper tous les Exceptions et vérifier le type. Je pensais que cela nettoyait le code, mais quelque chose me faisait revenir à la question et j'ai lu les autres réponses à la question. Je l'ai mâchée pendant un moment, mais je dois être d'accord avec vous. Il est plus lisible et maintenable d'utiliser une fonction pour sécher votre code que pour tout récupérer, vérifier le type en comparant avec une liste, en encapsulant le code et en lançant. Merci d'être venu en retard et d'avoir fourni une option alternative et saine (IMO). +1.
erreur

8
L'utilisation d'une fonction de gestion des erreurs ne fonctionnerait pas si vous vouliez inclure un throw; . Il faudrait répéter cette ligne de code dans chaque bloc catch (évidemment pas la fin du monde mais mérite d'être mentionné car c'est du code qui devrait être répété).
kad81

5
@ kad81, c'est vrai, mais vous auriez toujours l'avantage d'écrire le code de journalisation et de nettoyage en un seul endroit, et de le changer en un seul endroit si nécessaire, sans la sémantique maladroite d'attraper le type d'exception de base, puis de créer une branche basée sur le type d'exception. Et cette throw();instruction supplémentaire dans chaque bloc de capture est un petit prix à payer, OMI, et vous laisse toujours en mesure de faire un nettoyage supplémentaire spécifique au type d'exception si nécessaire.
Craig

2
Salut @Reitffunk, utilisez simplement Func<Exception, MyEnumType>au lieu de Action<Exception>. C'est Func<T, Result>, avec Resultle type de retour.
Craig

3
Je suis entièrement d'accord ici. J'ai lu moi aussi la première réponse et la pensée semble logique. Déplacé vers un générique 1 pour tous les gestionnaires d'exceptions. Quelque chose en moi m'a fait vomir intérieurement ... alors j'ai inversé le code. Puis je suis tombé sur cette beauté! Cela doit être la réponse acceptée
Conor Gallagher

372

Comme d'autres l'ont souligné, vous pouvez avoir une ifdéclaration à l'intérieur de votre bloc catch pour déterminer ce qui se passe. C # 6 prend en charge les filtres d'exception, donc les éléments suivants fonctionneront:

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

La MyFilterméthode 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)
{
    
}

Ceci est différent de l'utilisation d'une ifinstruction dans lecatch bloc, 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 de ce document, cela inclura la prise en charge de C # 6.

Le référencement de ce package entraînera la génération du projet à l'aide de la version spécifique des compilateurs C # et Visual Basic contenus dans le package, par opposition à toute version installée du système.


3
En attendant patiemment la sortie officielle de 6 ... J'aimerais que cela devienne le checky quand cela se produit.
RubberDuck

@RubberDuck Je meurs d'envie pour l'opérateur de propagation nulle de C # 6. Essayer de convaincre le reste de mon équipe que le risque d'un langage / compilateur instable en vaut la peine. Beaucoup d'améliorations mineures avec un impact énorme. Quant à être marqué comme réponse, pas important, tant que les gens réalisent que cela sera / est possible, je suis heureux.
Joe

Droite?! Je vais jeter un œil à ma base de code dans un avenir proche. =) Je sais que le chèque n'est pas important, mais étant donné que la réponse acceptée sera bientôt dépassée, j'espère que OP reviendra le vérifier pour lui donner la bonne visibilité.
RubberDuck

C'est en partie pourquoi je ne l'ai pas encore attribué @Joe. Je veux que cela soit visible. Vous voudrez peut-être ajouter un exemple de filtre en ligne pour plus de clarté.
RubberDuck

188

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 pourriez faire, c'est utiliser une fonction anonyme pour encapsuler votre code d'erreur, puis l'appeler dans ces blocs catch spécifiques:

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

26
Idée intéressante et un autre exemple que VB.net a parfois des avantages intéressants par rapport à C #
Michael Stum

47
@MichaelStum avec ce genre de syntaxe que je qualifierais à peine d'intéressant ... frisson
MarioDS

17
Les filtres d'exception arrivent en c # 6! Notez la différence d'utiliser des filtres en faveur de la relance de roslyn.codeplex.com/discussions/541301
Arne Deruwe

@ArneDeruwe Merci pour ce lien! Je viens d'apprendre une autre raison importante de ne pas relancer: throw e;détruit stacktrace et callstack, throw;détruit "uniquement" callstack (rendant les vidages sur incident inutiles!) Une très bonne raison de ne pas utiliser l'un ou l'autre si cela peut être évité!
AnorZaken

1
Des filtres d'exception C # 6 sont disponibles! Finalement.
Danny

134

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'exceptions et renvoie false si le format est incorrect, en 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 pour analyser les tuples de retour, qui ne sont 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 externes est implémentée en C # 12. :)


19
Précisément - concis, et vous contournez totalement la pénalité de performance liée à la gestion de l'exception, la mauvaise forme d'utilisation intentionnelle des exceptions pour contrôler le flux du programme, et le flou de la propagation de votre logique de conversion, un peu ici et un peu là .
Craig

9
Je sais ce que tu voulais dire, mais bien sûr Guid.TryParsene revient jamais Guid.Empty. Si la chaîne est dans un format incorrect, elle définit le resultparamètre de sortie sur Guid.Empty, mais elle revient false . Je le mentionne parce que j'ai vu du code qui fait les choses dans le style de Guid.TryParse(s, out guid); if (guid == Guid.Empty) { /* handle invalid s */ }, ce qui est généralement faux s'il spourrait s'agir de la représentation sous forme de chaîne de Guid.Empty.

14
wow vous avez répondu à la question, sauf que ce n'est pas dans l'esprit de la question. Le plus gros problème est autre chose :(
nawfal

6
Le modèle approprié pour utiliser TryParse, bien sûr, ressemble davantage à if( Guid.TryParse(s, out guid){ /* success! */ } else { /* handle invalid s */ }ce qui ne laisse aucune ambiguïté comme l'exemple cassé où la valeur d'entrée pourrait en fait être la représentation sous forme de chaîne d'un Guid.
Craig

2
Cette réponse peut en effet être correcte en ce qui concerne Guid.Parse, mais elle a manqué tout le point de la question initiale. Ce qui n'avait rien à voir avec Guid.Parse, mais concernait la capture d'Exception vs FormatException / OverflowException / etc.
Conor Gallagher

115

Les filtres d'exception sont désormais 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;
}

Dans C # 7.0+, vous pouvez également combiner cela 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..? 
}

Cette méthode est préférée non seulement parce qu'elle est simple et claire, mais qu'elle n'a pas non plus à dérouler la pile si les conditions ne sont pas remplies, ce qui offre de meilleures performances et des informations de diagnostic comparées à la relance.
joe

74

Si vous pouvez mettre à jour votre application en C # 6, vous avez de la chance. La nouvelle version C # a implémenté des 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 fait, c'est la seule nouvelle fonctionnalité de C # 6 qui n'est pas possible d'émuler dans les versions précédentes. Tout d'abord, une relance signifie plus de frais généraux que de sauter le crochet. Deuxièmement, il 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 une discussion à ce sujet sur CodePlex . Et un exemple montrant la différence .


4
Lancer sans exception préserve la pile, mais "throw ex" la remplacera.
Ivan

32

Si vous ne souhaitez pas utiliser un if déclaration dans les catchchamps, en C# 6.0vous pouvez utiliser la Exception Filterssyntaxe qui a déjà été pris en charge par le CLR dans les versions des avant - premières , mais n'a existé 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'attrapera Exceptionque lorsqu'il s'agit d'un InvalidDataExceptionou ArgumentNullException.

En fait, vous pouvez mettre pratiquement n'importe quelle condition à l'intérieur de cette whenclause:

static int a = 8;

...

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

Notez que contrairement à un if instruction à l'intérieur de la catchportée de, Exception Filtersne peut pas lancer Exceptions, et quand ils le font, ou lorsque la condition ne l'est pas true, la catchcondition suivante 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: prise générale.

Lorsqu'il y en 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: Catch.

Et comme vous pouvez le voir dans le MSILcode, le code n'est pas traduit en ifinstructions, mais en Filterset Exceptionsne peut pas être jeté à partir des zones marquées par Filter 1et Filter 2mais le filtre lançant le Exceptionéchouera à la place, également la dernière valeur de comparaison poussée dans la pile avant la endfiltercommande déterminera le succès / l'échec du filtre ( Catch 1 XOR Catch 2 s'exécutera en conséquence):

Filtres d'exception MSIL

Aussi, a spécifiquement Guidla Guid.TryParseméthode.


+1 pour afficher plusieurs filtres lorsque et fournir une explication de ce qui se passe lorsque plusieurs filtres sont utilisés.
steven87vt

26

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

Et avec C # 8 comme expression de commutateur:

catch (Exception ex)
{
    WebId = ex switch
    {
        _ when ex is FormatException || ex is OverflowException => Guid.Empty,
        _ => throw ex
    };
}

3
Cela devrait être la réponse acceptée à partir de 2018 à mon humble avis.
MemphiZ

6
La réponse de Mat J en utilisant whenest beaucoup plus élégante / appropriée qu'un interrupteur.
rgoliveira

@rgoliveira: Je suis d'accord que pour le cas posé dans la question, la réponse de Mat J est plus élégante et appropriée. Cependant, il devient difficile à lire si vous avez un code différent que vous souhaitez exécuter en fonction du type d'exception ou si vous souhaitez réellement utiliser l'instance de l'exception. Tous ces scénarios peuvent être traités également avec cette instruction switch.
Fabian

1
@Fabian "si vous avez un code différent que vous voulez exécuter en fonction du type d'exception ou si vous voulez réellement utiliser l'instance de l'exception", alors vous faites juste un autre catch bloc , ou vous devrez quand même le caster .. D'après mon expérience, un throw;dans votre catchbloc est probablement une odeur de code.
rgoliveira

@rgoliveira: L'utilisation d'un lancer dans un bloc catch est OK dans plusieurs cas, voir le lien . Étant donné que l'instruction case utilise en fait un lien de correspondance de modèle , vous n'avez pas besoin de transtyper si vous remplacez le lien d' opérateur de suppression (le trait de soulignement) par un nom de variable. Ne vous méprenez pas, je suis d'accord avec vous que les filtres d'exception sont une façon plus propre de le faire, mais plusieurs blocs de capture ajoutent beaucoup de crochets.
Fabian

20

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

De plus, il semble que l'opérateur "is" puisse dégrader légèrement les performances.

CA1800: Ne jetez pas inutilement dit de "envisager de tester le résultat de l'opérateur" as "à la place", mais si vous le faites, vous écrirez plus de code que si vous interceptez 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
}

19
Mais sachez que vous ne pouvez pas renvoyer l'exception sans perdre la trace de la pile si vous le faites comme ceci. (Voir le commentaire de Michael Stum sur la réponse acceptée)
René

2
Ce modèle peut être amélioré en stockant l'exception (veuillez excuser la mauvaise mise en forme - je ne peux pas comprendre comment mettre du code dans les commentaires): Exception ex = null; essayez {// quelque chose} catch (FormatException e) {ex = e; } catch (OverflowException e) {ex = e; } if (ex! = null) {// autre chose et traiter avec ex}
Jesse Weigert

3
@JesseWeigert: 1. Vous pouvez utiliser des crochets pour donner à un morceau de texte une police à interligne simple et un fond gris clair. 2. Vous ne pourrez toujours pas renvoyer l'exception d'origine, y compris le stacktrace .
Oliver

2
@CleverNeologism, même s'il est vrai que l'utilisation de l' isopérateur peut avoir un léger impact négatif sur les performances, il est également vrai qu'un gestionnaire d'exceptions n'est pas le lieu d'être trop préoccupé par l'optimisation des performances. Si votre application passe tellement de temps dans les gestionnaires d'exceptions que l'optimisation des performances ferait une réelle différence dans les performances de l'application, il y a d'autres problèmes de code à examiner attentivement. Cela dit, je n'aime toujours pas cette solution car vous perdez la trace de la pile et parce que le nettoyage est contextuellement supprimé de l'instruction catch.
Craig

3
Le seul moment où l' isopérateur dégrade les performances est si vous effectuez une asopération ultérieurement (d'où ils qualifient la règle inutilement ). Si tout ce que vous faites est de tester le plâtre sans avoir réellement besoin de le faire, alors l' isopérateur est exactement ce que vous voulez utiliser.
saluce

19

en C # 6 l'approche recommandée est d'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

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 plantent votre programme, ajoutez simplement ceci APRÈS 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
}

-1 Ceci s'exécutera WebId = Guid.Emtpydans le cas où aucune exception n'a été levée.
septembre

4
@sepster Je pense que la déclaration de retour après "// quelque chose" est implicite ici. Je n'aime pas vraiment la solution, mais c'est une variante constructive de la discussion. +1 pour annuler votre downvote :-)
toong

@Sepster toong a raison, j'ai supposé que si vous vouliez un retour là-bas, vous en mettriez un ... J'essayais de rendre ma réponse suffisamment générale pour s'appliquer à toutes les situations au cas où d'autres avec des questions similaires mais pas exactes bénéficieraient comme bien. Cependant, pour faire bonne mesure, j'ai ajouté un returnà ma réponse. Merci pour la contribution.
bsara

18

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

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 déclarations «jeter» ou «retourner» 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 joli)

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

    // Handle exception
}

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

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

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

2
+1, c'est la meilleure réponse. C'est mieux que la réponse principale, principalement parce qu'il n'y a pas return, bien que l'inversion de la condition soit également un peu meilleure.
DCShannon

Je n'y ai même pas pensé. Bonne prise, je vais l'ajouter à la liste.
Stefan T

16

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


21
Pourquoi ne pas simplement utiliser le mot clé "is"?
Chris Pietschmann

29
@Michael - Si Microsoft a introduit, disons, StringTooLongException dérivé de FormatException, il s'agit toujours d'une exception de format, juste une exception spécifique. Cela dépend si vous voulez la sémantique de «intercepter ce type d'exception exact» ou «intercepter les exceptions qui signifient que le format de la chaîne était incorrect».
Greg Beech

6
@Michael - Notez également que "catch (FormatException ex) a la dernière sémantique, il interceptera tout ce qui dérive de FormatException.
Greg Beech

14
@Alex No. "throw" sans "ex" porte l'exception d'origine, y compris la trace de pile d'origine, vers le haut. L'ajout de "ex" fait réinitialiser la trace de la pile, vous obtenez donc vraiment une exception différente de l'original. Je suis sûr que quelqu'un d'autre peut l'expliquer mieux que moi. :)
Samantha Branham

13
-1: Ce code est extrêmement fragile - un développeur de bibliothèque peut s'attendre à le remplacer throw new FormatException();par throw new NewlyDerivedFromFormatException();sans casser le code en utilisant la bibliothèque, et cela restera vrai pour tous les cas de gestion des exceptions, sauf lorsque quelqu'un l'a utilisé à la ==place de is(ou simplement catch (FormatException)).
Sam Harwell

13

Que diriez-vous

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

Cela ne fonctionne que si le Catch-Code peut être entièrement déplacé dans le Try-Block. Mais l'imagerie du code où vous effectuez plusieurs manipulations sur un objet, et une au milieu échoue, et vous souhaitez "réinitialiser" l'objet.
Michael Stum

4
Dans ce cas, j'ajouterais une fonction de réinitialisation et l'appellerais depuis plusieurs blocs catch.
Maurice

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

11

Attention et averti: Encore un autre style fonctionnel et aimable.

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

(Fournir essentiellement une autre Catchsurcharge vide qui se retourne)

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


1
Un avantage possible de cette approche est qu'il y a une différence sémantique entre intercepter et renvoyer une exception par rapport à ne pas l'attraper; dans certains cas, le code doit agir sur une exception sans l' attraper. Une telle chose est possible dans vb.net, mais pas en C # sauf si l' on utilise un wrapper écrit en vb.net et appelé depuis C #.
supercat

1
Comment agir sur une exception sans la rattraper? Je ne vous comprends pas bien.
nawfal

@nawful ... using vb filter - function filt (ex as exception): LogEx (ex): return false ... then in the catch line: catch ex when filt (ex)
FastAl

1
@FastAl N'est-ce pas ce que les filtres d'exception permettent en C # 6?
HimBromBeere

@HimBromBeere yep ce sont des analogues directs
FastAl

9

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

Destiné aux personnes qui souhaitent une solution plus élégante pour intercepter une 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 à d'autres fins, mais elle fonctionnait parfaitement pour typevérifier les 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 des exceptions explicites, donc le ex is ...comportement était indésirable car les classes dérivées sont attribuables aux types parents).

Usage

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

Extension IsAnyOf.cs (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 de gestion complète des erreurs (copier-coller dans la nouvelle application 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 Exceptiontypes est exact (c'est-à-dire qu'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*/
            }
        }
    }
}

1
Améliorer la langue n'est pas "plus élégant". Dans de nombreux endroits, cela a en fait créé un enfer de maintenance. Des années plus tard, de nombreux programmeurs ne sont pas fiers du monstre qu'ils ont créé. Ce n'est pas ce que vous avez l'habitude de lire. Cela peut provoquer un "hein?" effet, ou même des «WTF» sévères. C'est parfois déroutant. La seule chose qu'il fait est de rendre le code beaucoup plus difficile à saisir pour ceux qui ont besoin de le traiter plus tard dans la maintenance - uniquement parce qu'un seul programmeur a essayé d'être "intelligent". Au fil des ans, j'ai appris que ces solutions «intelligentes» sont rarement aussi les bonnes.
Kaii

1
ou en quelques mots: restez fidèle aux possibilités offertes par la langue nativement. n'essayez pas de remplacer la sémantique d'une langue, uniquement parce que vous ne les aimez pas. Vos collègues (et peut-être moi-même) vous remercieront honnêtement.
Kaii

Notez également que votre solution ne fait qu'approcher la sémantique du C # 6 when, comme le ferait n'importe quelle version de catch (Exception ex) {if (...) {/*handle*/} throw;}. La valeur réelle de whenest que le filtre s'exécute avant que l'exception ne soit interceptée , évitant ainsi la corruption de dépenses / pile d'un re-throw. Il tire parti d'une fonctionnalité CLR qui n'était auparavant accessible qu'à VB et MSIL.
Marc L.

Plus élégant? Cet exemple est si grand pour un problème aussi simple et le code est si horrible qu'il ne valait même pas la peine d'y jeter un œil. Veuillez ne pas faire de ce code le problème de quelqu'un d'autre sur un projet réel.
KthProg

toute votre IsAnyOfméthode peut être réécrite simplementp_comparisons.Contains(p_parameter)
maksymiuk

7

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

Donc, ce que nous voudrions vraiment faire, c'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
    }

La raison pour laquelle nous voulons cela est parce que nous ne voulons pas que le gestionnaire d'exceptions intercepte 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ébogage, laideur)

Alors pourquoi ce code ne compile-t-il pas - et comment pouvons-nous le pirater de telle manière qu'il le fera?

Si nous regardons 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 comme cela, ce qui dans ce cas est logique car cela impliquerait que l'objet «exception» peut avoir différents types.

Ou pour l'écrire dans le 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 se compile pas est tout à fait évidente: quel type et quelle valeur aurait l'objet '$ exception' (qui sont ici stockés dans les variables 'e')? La façon dont nous voulons que le compilateur gère cela est de 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 qui sont interceptées. La façon dont cela est implémenté dans IL est comme un «filtre», qui est disponible dans VB.Net.

Pour le faire fonctionner 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 quelques 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 relancer correctement, et - soyons honnêtes - c'est tout à fait la solution laide. La laideur peut être corrigée un peu en effectuant l'élimination des branches, ce qui rend la solution légèrement meilleure:

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 ne laisse que le «re-throw». Pour que cela fonctionne, nous devons être en mesure d'effectuer la gestion à l'intérieur du bloc «catch» - et la seule façon de faire fonctionner cela est par un objet «Exception» de 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 la façon 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 à intercepter 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 d'attraper les bonnes exceptions et de les stocker dans un fichier temporaire.
  • Si le gestionnaire est simple et que nous voulons réutiliser le code, la meilleure solution est probablement d'introduire une fonction d'aide.
  • Si nous voulons relancer, nous n'avons pas d'autre choix que de mettre le code dans un gestionnaire de capture «Exception», ce qui cassera FxCop et les exceptions non capturées de votre débogueur.

7

C'est un problème classique auquel tous les développeurs C # sont 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 nous amè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 ()?

Compte tenu de votre échantillon spécifique, où la valeur de secours est bon marché à construire, j'aime suivre ces étapes:

  1. Initialisez WebId à la valeur de secours.
  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 est beaucoup moins chère, 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);
}

C'est un bon "codage écologique", c'est-à-dire que vous pensez à l'avance à votre empreinte de code et de données et que vous vous assurez qu'il n'y a aucune fuite de valeurs à moitié traitées. Ravi de suivre ce modèle grâce à Jeffrey!
Tahir Khalid

6

Vous répétez donc beaucoup de code dans chaque commutateur d'exception? On dirait que l'extraction d'une méthode serait une idée divine, 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.

De C # 6, vous avez en outre les filtres d'exception comme déjà mentionné 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); 
}

3
"Je me demande pourquoi personne n'a remarqué cette duplication de code." - Quoi? L' intérêt de la question est d'éliminer la duplication de code.
Mark Amery

4

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

Par exemple , si vous utilisez un « fourre-tout » exception comme exception il précédera toutes les autres déclarations de captures et vous aurez évidemment obtenir des erreurs compilateur si vous inversez l'ordre que vous pouvez enchaîner vos déclarations de captures (peu d'un anti-modèle que je pense ), vous pouvez mettre le type d' exception fourre-tout en bas et ce sera capturer toutes les exceptions qui ne répondaient pas 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 fortement aux gens de lire ce document MSDN:

Hiérarchie des exceptions


4

Peut-être essayez-vous de garder votre code simple, par exemple en mettant le code commun dans une méthode, comme vous le feriez dans toute 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 et magnifique


3

Notez que j'ai trouvé une façon 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;
    }
}

9
-1 vote, +5 WTF :-) Cela n'aurait pas dû être marqué comme réponse, mais il est larié.
Aaron

1
Peu importe à quel point nous pourrions le faire simplement. Mais il n'est pas resté inactif et a proposé sa solution. Vraiment apprécié.
Maxymus

2
Ne faites pas cela cependant, utilisez des filtres d'exception en C # 6 ou l'une des autres réponses - je mets cela ici spécifiquement comme "C'est une façon, mais c'est mauvais et je veux faire quelque chose de mieux".
Michael Stum

POURQUOI est-ce mauvais? J'étais perplexe que vous ne puissiez pas utiliser l'exception dans une instruction switch directement.
MKesper

3
@MKesper Je vois quelques raisons pour lesquelles c'est mauvais. Cela nécessite d'écrire les noms de classe complets sous forme de littéraux de chaîne, ce qui est vulnérable aux fautes de frappe dont le compilateur ne peut pas vous sauver. (Ceci est important car dans de nombreux magasins, les cas d'erreur sont moins bien testés et il est donc plus probable que des erreurs triviales soient manquées.) Il ne correspondra pas non plus à une exception qui est une sous - classe de l'un des cas spécifiés. Et, en raison d'être des chaînes, les cas seront manqués par des outils comme "Find All References" de VS - pertinents si vous voulez ajouter une étape de nettoyage partout où une exception particulière est interceptée.
Mark Amery

2

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

J'ai rencontré un scénario de cas d'utilisation en essayant de caster un objet de contrôle dans une grille de données, avec un contenu comme 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

Je veux suggérer la réponse la plus courte (un style plus 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 nuget, 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;
        }
    }

ps Je n'ai pas mis de vérification nulle pour la simplicité du code, pensez à ajouter des validations de paramètres.

ps2 Si vous souhaitez renvoyer une valeur de catch, il est nécessaire de faire les mêmes méthodes Catch, mais avec des retours et Func au lieu d'Action dans les paramètres.


-15

Il suffit d'appeler l'essai et d'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 !!


3
euh. cela va à l'encontre du but de la question. Il pose cette question pour se débarrasser du code en double. cette réponse ajoute plus de code en double.
James Esh

-23

Dans c # 6.0, les filtres d'exception sont des améliorations pour 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");
    }
}

13
Cet exemple ne montre aucune utilisation de filtres d'exception.
user247702

Ceci est un moyen standard de filtrer les exceptions dans c # 6.0
Kashif

5
Revoyez ce que sont exactement les filtres d'exception. Vous n'utilisez pas de filtre d'exception dans votre exemple. Il y a un bon exemple dans cette réponse publiée un an avant la vôtre.
user247702

6
Un exemple de filtrage d'exception seraitcatch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }
saluce
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.