Ce qui suit est correct:
try
{
Console.WriteLine("Before");
yield return 1;
Console.WriteLine("After");
}
finally
{
Console.WriteLine("Done");
}
Le finally
bloc s'exécute lorsque le tout a fini de s'exécuter ( IEnumerator<T>
prend IDisposable
en charge pour fournir un moyen de garantir cela même lorsque l'énumération est abandonnée avant la fin).
Mais ce n'est pas correct:
try
{
Console.WriteLine("Before");
yield return 1; // error CS1626: Cannot yield a value in the body of a try block with a catch clause
Console.WriteLine("After");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Supposons (pour des raisons d'argumentation) qu'une exception soit levée par l'un ou l'autre des WriteLine
appels à l'intérieur du bloc try. Quel est le problème avec la poursuite de l'exécution en catch
bloc?
Bien sûr, la partie yield return est (actuellement) incapable de lancer quoi que ce soit, mais pourquoi cela devrait-il nous empêcher d'avoir une clôture try
/ catch
traiter les exceptions levées avant ou après un yield return
?
Mise à jour: Il y a un commentaire intéressant d'Eric Lippert ici - il semble qu'ils aient déjà suffisamment de problèmes pour implémenter correctement le comportement try / finally!
EDIT: La page MSDN sur cette erreur est: http://msdn.microsoft.com/en-us/library/cs1x15az.aspx . Cela n'explique cependant pas pourquoi.
la source
Réponses:
Je soupçonne que c'est une question de praticité plutôt que de faisabilité. Je soupçonne qu'il y a très, très peu de fois où cette restriction est en fait un problème qui ne peut pas être contourné - mais la complexité supplémentaire dans le compilateur serait très importante.
Il y a quelques choses comme celle-ci que j'ai déjà rencontrées:
Dans chacun de ces cas, il serait possible de gagner un peu plus de liberté, au prix d'une complexité supplémentaire dans le compilateur. L'équipe a fait le choix pragmatique, pour lequel je les applaudis - je préfère avoir un langage un peu plus restrictif avec un compilateur précis à 99,9% (oui, il y a des bugs; j'en ai rencontré un sur SO l'autre jour) qu'un plus langage flexible qui n'a pas pu se compiler correctement.
EDIT: Voici une pseudo-preuve de la raison pour laquelle c'est faisable.
Considérez cela:
Maintenant, transformez:
into (sorte de pseudo-code):
La seule duplication est dans la configuration des blocs try / catch - mais c'est quelque chose que le compilateur peut certainement faire.
J'ai peut-être manqué quelque chose ici - si oui, faites-le moi savoir!
la source
using
etforeach
. Par exemple:try{foreach (string s in c){yield return s;}}catch(Exception){}
yield
, à mon avis, à cause du code spaghetti que vous devez écrire pour le contourner.yield
OMI - c'est loin d'être sévère .Toutes les
yield
instructions d'une définition d'itérateur sont converties en un état dans une machine à états qui utilise effectivement uneswitch
instruction pour avancer les états. Si elle a fait générer du code pour lesyield
déclarations dans un try / catch il faudrait dupliquer tout dans letry
bloc pour chaqueyield
déclaration tout en excluant toute autreyield
déclaration pour ce bloc. Ce n'est pas toujours possible, en particulier si uneyield
instruction dépend d'une précédente.la source
Je suppose qu'en raison de la façon dont la pile d'appels est enroulée / déroulée lorsque vous donnez un retour d'un énumérateur, il devient impossible pour un bloc try / catch de réellement "capturer" l'exception. (parce que le bloc de retour de rendement n'est pas sur la pile, même s'il est à l'origine du bloc d'itération)
Pour avoir une idée de ce dont je parle, configurez un bloc d'itérateur et un foreach en utilisant cet itérateur. Vérifiez à quoi ressemble la pile d'appels à l'intérieur du bloc foreach, puis vérifiez-la dans le bloc try / finally de l'itérateur.
la source
J'ai accepté la réponse de THE INVINCIBLE SKEET jusqu'à ce que quelqu'un de Microsoft vienne verser de l'eau froide sur l'idée. Mais je ne suis pas d'accord avec la partie question d'opinion - bien sûr, un compilateur correct est plus important qu'un compilateur complet, mais le compilateur C # est déjà très intelligent pour trier cette transformation pour nous dans la mesure où il le fait. Un peu plus d'exhaustivité dans ce cas rendrait le langage plus facile à utiliser, à enseigner, à expliquer, avec moins de cas extrêmes ou de pièges. Je pense donc que cela en vaudrait la peine. Quelques gars à Redmond se grattent la tête pendant une quinzaine de jours et, par conséquent, des millions de codeurs au cours de la prochaine décennie peuvent se détendre un peu plus.
(Je nourris aussi un désir sordide qu'il y ait un moyen de faire
yield return
jeter une exception qui a été insérée dans la machine à états "de l'extérieur", par le code conduisant l'itération. Mais mes raisons de vouloir cela sont assez obscures.)En fait, une question que j'ai à propos de la réponse de Jon est liée au lancement de l'expression de retour de rendement.
De toute évidence, le rendement de rendement 10 n'est pas si mal. Mais ce serait mauvais:
Il ne serait donc pas plus logique d'évaluer cela dans le bloc try / catch précédent:
Le problème suivant serait les blocs try / catch imbriqués et les exceptions relancées:
Mais je suis sûr que c'est possible ...
la source