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.