56 votes

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

En C# 6.0, le mot-clé "when" a été introduit. Il est désormais possible de 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, ne s'agit-il pas simplement d'un sucre syntaxique ou j'ai raté 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) {…}

73voto

Eric Lippert Points 300275

En plus des nombreuses et excellentes réponses que vous avez déjà trouvées ici, il y a un différence très importante entre un filtre d'exception et un "si" dans un bloc catch : les filtres sont exécutés avant les blocs internes "finally .

Considérez 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é. Elle appelle N(), qui appelle MakeAMess(). Un désordre est créé. Puis DoSomethingDangerous() lève MonException. Le runtime vérifie s'il y a un bloc catch qui peut s'en charger, et c'est le cas. Le bloc finally exécute CleanItUp(). Le désordre est nettoyé. Le contrôle passe au bloc catch. Et le bloc catch appelle F() et ensuite, peut-être, C().

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

Avez-vous remarqué la différence ? Dans le cas du M1, F() est appelé après que le désordre soit nettoyé et dans le cas du M2, elle est appelée avant le désordre est nettoyé. Si F() dépend de l'absence de désordre pour être correct, vous aurez de gros problèmes si vous remaniez M1 pour qu'il ressemble à M2 !

Il y a plus que des problèmes de justesse ici ; il y a aussi des implications de sécurité. Supposons que le "désordre" que nous faisons est "usurper l'identité de l'administrateur", que l'opération dangereuse nécessite un accès administrateur, et que le nettoyage enlève l'identité de l'administrateur. Dans M2, l'appel à F a des droits d'administrateur . Dans M1, ce n'est pas le cas. Supposons que l'utilisateur a accordé peu de privilèges à l'assemblage contenant M2 mais que N est dans un assemblage de confiance totale ; un code potentiellement hostile dans l'assemblage de M2 pourrait obtenir un accès administrateur par cette attaque de leurre.

À titre d'exercice : comment écrivez-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 des annotations de la pile qui accordent ou refusent des privilèges entre M2 et N, et il les annule avant d'appeler F. C'est un désordre que le runtime a créé et il sait comment le gérer correctement. Mais le runtime n'est pas au courant d'une autre pagaille que vous fait.)

Ce qu'il faut retenir, c'est qu'à chaque fois que vous traitez une exception, par définition, quelque chose s'est mal passé et le monde n'est pas comme vous le pensez. Les filtres d'exception ne doivent pas dépendre, pour leur exactitude, d'invariants qui ont été violés par la condition exceptionnelle.

UPDATE :

Ian Ringrose demande comment nous nous sommes mis 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 mon départ de Microsoft en 2012. Cependant, j'ai discuté de ces questions à plusieurs reprises avec les concepteurs du langage et je pense pouvoir donner un résumé juste de la situation.

La décision de conception de faire fonctionner les filtres avant les blocs finaux a été prise dans les tout premiers jours du CLR ; la personne à qui demander si vous voulez les petits détails de cette décision de conception serait Chris Brumme. (UPDATE : Malheureusement, Chris n'est plus disponible pour répondre aux 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. Pour des raisons de débogage, nous voulons savoir avant les blocs finally exécutent si cette exception va être gérée, ou si nous sommes dans le scénario du "comportement indéfini" d'une exception complètement non gérée détruisant le processus. Parce que si le programme est exécuté dans un débogueur, ce comportement indéfini va inclure la rupture au point de l'exception non gérée. avant l'exécution des blocs finaux.

Le fait que cette sémantique introduit des problèmes de sécurité et de correction a été très bien compris par l'équipe CLR ; en fait, j'en ai parlé dans mon premier livre, qui a été expédié 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 radical que de "corriger" la sémantique maintenant.

Cette fonctionnalité a toujours existé dans 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é dans C# n'ajoute pas de 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 qu'à ma connaissance elle n'ait jamais été à l'origine d'un problème de sécurité sérieux prouve que ce n'est pas une voie très fructueuse pour les attaquants.

Pourquoi alors cette fonctionnalité était-elle présente dans la première version de VB.NET et a mis plus de dix ans à s'imposer dans C# ? Eh bien, il est difficile de répondre à ce genre de questions "pourquoi pas", mais dans ce cas, je peux le résumer assez facilement : (1) nous avions bien d'autres choses en tête, et (2) Anders trouve cette fonctionnalité peu attrayante. (Et elle ne m'enchante pas non plus.) Cela l'a relégué au bas de la liste des priorités pendant de nombreuses années.

Comment, dans ce cas, est-elle parvenue à figurer suffisamment haut sur la liste des priorités pour être implémentée dans C# 6 ? De nombreuses personnes ont demandé cette fonctionnalité, ce qui est toujours un argument en faveur de sa réalisation. VB l'avait déjà, et les équipes C# et VB aiment avoir la parité quand c'est possible à un coût raisonnable, donc c'est aussi un point. Mais le grand point de bascule a été : il y avait un scénario dans le projet Roslyn lui-même où des filtres d'exception auraient été vraiment utiles. (Je ne me souviens pas de ce que c'était ; allez plonger dans le code source si vous voulez le trouver et faites un rapport).

En tant que concepteur de langage et auteur de compilateur, vous devez veiller à pas prioriser les caractéristiques qui font uniquement La plupart des utilisateurs de C# ne sont pas des rédacteurs de compilateurs, et ce sont eux les clients ! Mais en fin de compte, le fait de disposer d'un ensemble de scénarios réels où la fonctionnalité est utile, y compris certains qui irritaient l'équipe du compilateur elle-même, a fait pencher la balance.

46voto

Tim Schmelter Points 163781

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

Non, parce que votre deuxième approche sans when n'atteindra pas le second Catch si le ex.Status== WebExceptionStatus.SendFailure . Avec when le premier Catch auraient été ignorés.

Ainsi, 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 site else throw permettra de s'assurer que seuls WebExceptions avec status=Timeout ou SendFailure sont traitées ici, de manière similaire à la when approche. Toutes les autres ne seront pas traitées et l'exception sera propagée. Notez que l'exception ne sera pas rattrapée par la dernière version de Catch donc il y a toujours une différence avec le système de gestion de l'information. when . Cela montre l'un des avantages de when .

11voto

Phill W. Points 251

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 lancement des exceptions.

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

Le premier "throw" (ces exceptions de "première chance" dont 'Studio parle sans cesse) indique au Run-Time qu'il doit localiser le fichier le plus proche Gestionnaire d'exception qui peut traiter cette situation Type d'Exception et de rassembler tous les blocages "définitifs" entre "ici" et "là".

Le deuxième "throw" déroule la pile d'appels, en exécutant chacun de ces blocs "finally" à tour de rôle, puis livre le moteur d'exécution au point d'entrée du code de traitement des exceptions situé.

Auparavant, nous pouvions seulement faire la distinction entre différents Types d'exception. Ce décorateur nous donne grain plus fin en ne capturant qu'un type particulier d'exception qui se trouve dans un état que nous pouvons faire quelque chose .
Par exemple, vous pourriez vouloir gérer une "exception de base de données" qui indique une rupture de connexion et, lorsque cela se produit, essayer de se reconnecter automatiquement.
Lots d'opérations de la base de données lancent une "Exception de base de données", mais vous êtes seulement intéressé dans un " sous-type " particulier d'entre eux, sur la base de la propriétés de l'objet Exception, qui sont toutes disponibles pour le système qui lance l'exception.

Une instruction "if" à l'intérieur du bloc catch sera permet d'obtenir le même résultat final, mais il "coûtera" plus cher à l'exécution. Parce que ce bloc va attraper tous et chacun "Database Exceptions", il sera invoqué pour tous d'entre eux, même si elle ne peut faire quelque chose d'utile que pour une [très] petite fraction d'entre eux. Cela signifie également que vous devez ensuite rejeter [toutes] les exceptions que vous avez utilisées. ne peut pas faire quoi que ce soit d'utile avec, ce qui revient à répéter toute la farago en deux passes, la recherche de gestionnaire, la récolte finale, le lancement d'exception 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 les employés de la ville sont exonéré du péage (I a fait dire que c'était étrange), alors il suffit d'arrêter les voitures conduites par n'importe qui d'autre.

Vous pourriez arrêter chaque voiture et demander :

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

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

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

0voto

Mohit Shrivastava Points 11293

Extension de la réponse de Tim.

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

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

Le mot-clé when fonctionne comme if. Une condition when est une expression de prédicat, qui peut être ajoutée à un bloc catch. Si l'expression du 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 et mot-clé when en C# 6.0

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X