web-dev-qa-db-fra.com

Le mot clé "when" dans un bloc try catch est-il identique à une instruction if?

Dans C # 6.0, le mot clé "when" a été introduit, vous pouvez désormais filtrer une exception dans un bloc catch. Mais n'est-ce pas la même chose qu'une instruction if à l'intérieur d'un bloc catch? si c'est le cas, n'est-ce pas simplement du sucre syntaxique ou il me manque quelque chose?

Par exemple, un bloc try catch avec le mot clé "when":

try { … } 
catch (WebException ex) when ex.Status == WebExceptionStatus.Timeout {
   //do something
}
catch (WebException ex) when ex.Status== WebExceptionStatus.SendFailure {
   //do something
}
catch (Exception caught) {…}

Ou

try { … } 
catch (WebException ex) {
   if(ex.Status == WebExceptionStatus.Timeout) {
      //do something
   }
}
catch (WebException ex) {
   if(ex.Status == WebExceptionStatus.SendFailure) {
      //do something
   }
}
catch (Exception caught) {…}
56
Undeadparade

En plus des nombreuses bonnes réponses que vous avez déjà ici: il y a différence très importante entre un filtre d'exception et un "si" dans un bloc catch: les filtres s'exécutent avant que les blocs internes ne se bloquent finalement .

Considérer ce qui suit:

void M1()
{
  try { N(); } catch (MyException) { if (F()) C(); }
}
void M2()
{
  try { N(); } catch (MyException) when F() { C(); }
}
void N()
{
  try { MakeAMess(); DoSomethingDangerous(); } 
  finally { CleanItUp(); }
}

L'ordre des appels diffère entre M1 et M2 .

Supposons que M1 soit appelé. Il appelle N (), qui appelle MakeAMess (). Un gâchis est fait. Ensuite, DoSomethingDangerous () lève MyException. Le runtime vérifie s'il existe un bloc catch qui peut gérer cela, et c'est le cas. Le bloc finalement exécute CleanItUp (). Le désordre est nettoyé. Le contrôle passe au bloc de capture. Et le bloc catch appelle F() puis, peut-être, C ().

Et M2? Il appelle N (), qui appelle MakeAMess (). Un gâchis est fait. Ensuite, DoSomethingDangerous () lève MyException. Le runtime vérifie s'il existe un bloc catch qui peut gérer cela, et il y en a - peut-être. Le runtime appelle F() pour voir si le bloc catch peut le gérer, et il le peut. Le bloc finally exécute CleanItUp (), le contrôle passe au catch et C() est appelé.

Avez-vous remarqué la différence? Dans le cas M1, F() est appelé après le nettoyage du gâchis, et dans le cas M2, il est appelé avant le désordre est nettoyé. Si F() dépend de l'absence de gâchis pour son exactitude, vous avez de gros problèmes si vous refactorisez M1 pour ressembler à M2!

Il y a plus que de simples problèmes de correction ici; il y a aussi des implications pour la sécurité. Supposons que le "gâchis" que nous faisons est "usurper l'identité de l'administrateur", l'opération dangereuse nécessite un accès administrateur et le nettoyage supprime l'identité de l'administrateur. En M2, l'appel à F a des droits d'administrateur. Dans M1, ce n'est pas le cas. Supposons que l'utilisateur ait accordé peu de privilèges à l'assembly contenant M2 mais N se trouve dans un assembly de confiance totale; du code potentiellement hostile dans l'assembly M2 pourrait obtenir un accès administrateur via cette attaque de leurre.

En exercice: comment écririez-vous N pour qu'il se défende contre cette attaque?

(Bien sûr, le runtime est assez intelligent pour savoir s'il y a annotations de pile qui accordent ou refusent les privilèges entre M2 et N, et il les annule avant d'appeler F. C'est un gâchis que le runtime a fait et il sait comment y faire face correctement. Mais le runtime ne connaît aucun autre gâchis créé par vous.)

L'essentiel à retenir ici est qu'à chaque fois que vous gérez une exception, par définition, quelque chose a horriblement mal tourné, et le monde n'est pas ce que vous pensez qu'il devrait être. Les filtres d'exception ne doivent pas dépendre pour leur exactitude des invariants qui ont été violés par la condition exceptionnelle.

MISE À JOUR:

Ian Ringrose demande comment nous sommes entrés dans ce pétrin.

Cette partie de la réponse sera quelque peu conjecturale car certaines des décisions de conception décrites ici ont été prises après que j'ai quitté Microsoft en 2012. Cependant, j'ai discuté avec ces concepteurs de langage à plusieurs reprises et Je pense que je peux donner un bon résumé de la situation.

La décision de conception de faire fonctionner les filtres avant enfin les blocs a été prise au tout début du CLR; la personne à demander si vous voulez les petits détails de cette décision de conception serait Chris Brumme. (MISE À JOUR: Malheureusement, Chris n'est plus disponible pour les questions.) Il avait un blog avec une exégèse détaillée du modèle de gestion des exceptions, mais je ne sais pas s'il est toujours sur Internet.

C'est une décision raisonnable. À des fins de débogage, nous voulons savoir avant les blocs enfin s'exécutent si cette exception va être gérée, ou si nous sommes dans le scénario "comportement indéfini" d'une exception complètement non gérée détruisant le processus . Parce que si le programme s'exécute dans un débogueur, ce comportement non défini va inclure la rupture au point de l'exception non gérée avant les blocs enfin s'exécutent.

Le fait que cette sémantique introduit des problèmes de sécurité et d'exactitude a été très bien compris par l'équipe CLR; en fait, j'en ai parlé dans mon premier livre, qui a été publié il y a de nombreuses années maintenant, et il y a douze ans sur mon blog:

https://blogs.msdn.Microsoft.com/ericlippert/2004/09/01/finally-does-not-mean-immediately/

Et même si l'équipe du CLR le voulait, ce serait un changement de rupture de "corriger" la sémantique maintenant.

La fonctionnalité a toujours existé en CIL et VB.NET, et l'attaquant contrôle le langage d'implémentation du code avec le filtre, donc l'introduction de la fonctionnalité en C # n'ajoute aucune nouvelle surface d'attaque.

Et le fait que cette fonctionnalité qui introduit un problème de sécurité soit "dans la nature" depuis quelques décennies maintenant et à ma connaissance n'a jamais été la cause d'un grave problème de sécurité prouve que ce n'est pas une avenue très fructueuse pour les attaquants.

Pourquoi alors la fonctionnalité dans la première version de VB.NET a-t-elle pris plus d'une décennie pour en faire C #? Eh bien, il est difficile de répondre à de telles questions "pourquoi pas", mais dans ce cas, je peux le résumer assez facilement: (1) nous avions beaucoup d'autres choses en tête, et (2) Anders trouve la fonctionnalité peu attrayante. (Et je n'en suis pas ravi non plus.) Cela l'a déplacé au bas de la liste des priorités pendant de nombreuses années.

Comment l'a-t-il alors fait suffisamment haut sur la liste des priorités pour être implémenté en C # 6? Beaucoup de gens ont demandé cette fonctionnalité, ce qui est toujours en faveur de le faire. VB l'avait déjà, et les équipes C # et VB aiment avoir la parité lorsque cela est possible à un coût raisonnable, c'est donc des points également. Mais le gros point de basculement était : il y avait un scénario dans le projet Roslyn lui-même où les filtres d'exception auraient été vraiment utiles. (Je ne me souviens pas ce que c'était; allez plonger dans le code source si vous voulez le trouver et rapport!)

En tant que concepteur de langage et rédacteur de compilateur, vous devez faire attention à pas prioriser les fonctionnalités qui facilitent la vie de l'auteur de compilateur seulement; la plupart des utilisateurs C # ne sont pas des rédacteurs de compilateurs et ce sont des clients! Mais en fin de compte, avoir une collection de scénarios du monde réel où la fonctionnalité est utile, y compris certains qui irritaient l'équipe de compilation elle-même, a fait pencher la balance.

73
Eric Lippert

Mais n'est-ce pas la même chose qu'une instruction if à l'intérieur d'un bloc catch?

Non, car votre deuxième approche sans when n'atteindra pas la deuxième Catch si le ex.Status== WebExceptionStatus.SendFailure. Avec when, le premier Catch aurait été ignoré.

Donc, la seule façon de gérer le Status sans when est d'avoir la logique dans un catch:

try { … } 
catch (WebException ex) {
   if(ex.Status == WebExceptionStatus.Timeout) {
      //do something
   }
   else if(ex.Status == WebExceptionStatus.SendFailure) {
      //do something
   }
   else
      throw; // see Jeppe's comment 
}
catch (Exception caught) {…}

Le else throw s'assurera que seulement WebExceptions avec status=Timeout ou SendFailure sont traités ici, de la même manière que l'approche when. Tous les autres ne seront pas traités et l'exception sera propagée. Notez qu'il ne sera pas capturé par le dernier Catch, donc il y a toujours une différence avec le when. Cela montre l'un des avantages de when.

46
Tim Schmelter

n'est-ce pas la même chose qu'une instruction if à l'intérieur d'un bloc catch?

Non. Il agit plutôt comme un "discriminateur" au profit du système de levée d'exceptions.

Rappelez-vous comment les exceptions sont levées deux fois ?

Le premier "lancer" (ces exceptions de "première chance" que 'Studio poursuit') indique au run-time de localiser le gestionnaire d'exceptions le plus proche qui peut gérer ce type d'exception et collecter tous les blocs "finalement" entre "ici" et "là".

Le deuxième "lancer" déroule la pile d'appels, exécutant chacun de ces blocs "finalement" à son tour, puis remet le moteur d'exécution au point d'entrée du code de gestion des exceptions localisé.

Auparavant, nous ne pouvions distinguer que différents types d'exception. Ce décorateur nous donne un contrôle plus fin , n'attrapant qu'un type d'exception particulier qui se trouve être dans un - état que nous pouvons faire quelque chose .
Par exemple (à l'air libre), vous pouvez gérer une "exception de base de données" qui indique une connexion interrompue et, lorsque cela se produit, essayez de vous reconnecter automatiquement.
Beaucoup d'opérations de base de données génèrent une "exception de base de données", mais vous êtes seulement intéressé dans un "sous-type" particulier, basé sur les propriétés de l'objet Exception, qui sont toutes disponible pour le système de levée d'exceptions.

Une instruction "if" à l'intérieur du bloc catch obtiendra le même résultat final, mais elle "coûtera" plus cher au moment de l'exécution. Parce que ce bloc interceptera toutes et toutes "Exceptions de base de données", il sera invoqué pour toutes d'entre eux, même s'il ne peut faire quelque chose d'utile que pour une [très] petite fraction d'entre eux. Cela signifie également que vous devez ensuite relancer [toutes] les exceptions avec lesquelles vous ne pouvez pas faire quoi que ce soit d'utile, ce qui ne fait que répéter l'ensemble , deux passes, recherche de gestionnaire, enfin récolte, farago lanceur d'exceptions encore une fois.

Analogie: Un pont à péage [très étrange].

Par défaut, vous devez "attraper" chaque voiture pour qu'elle paie le péage. Si, par exemple, les voitures conduites par des employés de la ville sont exemptées du péage (I l'a fait dis que c'était étrange), alors il suffit d'arrêter les voitures conduites par quelqu'un d'autre.

Vous pouvez arrêter chaque voiture et demander:

catch( Car car ) 
{ 
   if ( car.needsToPayToll() ) 
      takePayment( car ); 
} 

Ou, si vous aviez un moyen "d'interroger" la voiture à l'approche, alors vous pourriez ignorer ceux conduits par les employés de la ville, comme dans:

catch( Car car ) when car.needsToPayToll() 
{ 
   takePayment( car ); 
} 
11
Phill W.

Prolonger la réponse de Tim.

C # 6.0 introduit un nouveau filtre d'exception de fonctionnalité et un nouveau mot-clé lorsque .

Le mot clé "when" dans un bloc try catch est-il le même qu'une instruction if?

Le mot clé when fonctionne comme si. Une condition when est une expression de prédicat, qui peut être ajoutée à un bloc catch. Si l'expression de prédicat est évaluée comme étant vraie, le bloc catch associé est exécuté; sinon, le bloc catch est ignoré.

Une merveilleuse explication est donnée sur Filtre d'exception C # 6.0 et quand mot-clé

0
Mohit Shrivastava