103 votes

Pourquoi le rendement ne peut-il pas apparaître dans un bloc try avec une capture?

Le suivant est correct:

try
{
    Console.WriteLine("Before");

    yield return 1;

    Console.WriteLine("After");
}
finally
{
    Console.WriteLine("Done");
}

L' finally bloc s'exécute lorsque la chose a fini de s'exécuter (IEnumerator<T> soutient IDisposable de fournir un moyen de veiller à ce même lorsque l'énumération est abandonné 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 que (pour la clarté de l'exposé) qu'une exception est levée par l'une ou l'autre de l' WriteLine des 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, le rendement de retour de la partie est (pour l'instant) incapable de jeter quoi que ce soit, mais pourquoi devrait-il nous empêcher d'avoir un enfermant try/catch pour traiter les exceptions levées avant ou après un yield return?

Mise à jour: Il y a un intéressant commentaire de Eric Lippert ici - semble qu'ils ont déjà suffisamment de problèmes de mise en œuvre de la try/finally comportement correctement!

EDIT: La page MSDN sur cette erreur est: http://msdn.microsoft.com/en-us/library/cs1x15az.aspx. Il n'explique pas pourquoi, si.

53voto

Jon Skeet Points 692016

Je suppose que c'est une question d'ordre pratique plutôt que de la 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 travaillé autour de mais la complexité ajoutée dans le compilateur serait très important.

Il y a quelques petites choses comme ce que j'ai déjà rencontré:

  • Les attributs de ne pas pouvoir être générique
  • L'incapacité de X pour tirer de X. Y (une classe imbriquée dans X)
  • Itérateur blocs à l'aide de champs publics dans les classes générées

Dans chacun de ces cas, il serait possible de gagner un peu plus de liberté, le coût supplémentaire de complexité dans le compilateur. L'équipe a fait le choix pragmatique, pour lequel je félicite - je préfère avoir un peu plus restrictive de la langue avec 99,9% de précision compilateur (oui, il y a des bugs, j'ai rencontré un sur DONC, juste l'autre jour) de plus de souplesse de la langue qui n'est pas compilé correctement.

EDIT: Voici une pseudo-preuve de la façon dont il pourquoi c'est faisable.

Considérer que:

  • Vous pouvez assurez-vous que le taux de retour lui-même, ne lance pas d'exception (précalculer la valeur, et vous êtes juste à la définition d'un champ et renvoie "true")
  • Vous êtes autorisé try/catch qui n'utilise pas de taux de retour dans un bloc itérateur.
  • Toutes les variables locales au bloc itérateur sont variables d'instance dans le générés de type, de sorte que vous pouvez librement passer le code, à de nouvelles méthodes

Maintenant transformer:

try
{
    Console.WriteLine("a");
    yield return 10;
    Console.WriteLine("b");
}
catch (Something e)
{
    Console.WriteLine("Catch block");
}
Console.WriteLine("Post");

dans (une sorte de pseudo-code):

case just_before_try_state:
    try
    {
        Console.WriteLine("a");
    }
    catch (Something e)
    {
        CatchBlock();
        goto case post;
    }
    __current = 10;
    return true;

case just_after_yield_return:
    try
    {
        Console.WriteLine("b");
    }
    catch (Something e)
    {
        CatchBlock();
    }
    goto case post;

case post;
    Console.WriteLine("Post");


void CatchBlock()
{
    Console.WriteLine("Catch block");
}

La seule duplication est dans la mise en place de blocs try/catch - mais c'est quelque chose que le compilateur peut certainement le faire.

Je peut très bien avoir manqué quelque chose ici - si oui, s'il vous plaît laissez-moi savoir!

5voto

Mark Cidade Points 53945

Tous les yield des déclarations dans un itérateur définition sont convertis à un état d'une machine d'état qui utilise efficacement un switch déclaration à l'avance les états. Si elle n'a générer du code pour yield des déclarations dans un try/catch, il aurait dupliquer tout dans l' try bloc pour chaque yield déclaration, tout en excluant tous les autres yield déclaration pour le bloc. Ce n'est pas toujours possible, en particulier si un yield déclaration est personne à charge sur un antérieur.

3voto

Diego Rivero Points 31

Il y a une assez bonne explication de cette décision de conception C # par Eric Lippert ici:

http://blogs.msdn.com/ericlippert/archive/2009/07/23/iterator-blocks-part-five-push-vs-pull.aspx

2voto

Radu094 Points 7796

Je présume qu'en raison de la façon dont la pile d'appel obtient de plaie ou déroulés, lorsque vous cédez le retour d'un agent recenseur, il devient impossible pour un bloc try/catch à fait "attraper" l'exception à la règle. (parce que le taux de retour de bloc n'est pas sur la pile, même si il est à l'origine de l'itération bloc)

Pour obtenir un ideea de ce que je parle de l'installation d'un itérateur bloc et un foreach l'aide que l'itérateur. Vérifiez que la Pile d'Appel ressemble à l'intérieur de la boucle foreach bloc, puis le vérifier à l'intérieur de l'itérateur try/finally bloc.

2voto

Daniel Earwicker Points 63298

J'ai accepté L'INVINCIBLE SKEET réponse jusqu'à ce que quelqu'un à partir de Microsoft vient de verser de l'eau froide sur l'idée. Mais je ne suis pas d'accord avec la question de l'opinion de la partie - bien sûr, un bon compilateur est plus important que de un complet, mais le compilateur C# est déjà très intelligents pour faire le tri dans cette transformation, pour nous, pour autant qu'il n'. Un peu plus à l'exhaustivité dans ce cas serait de rendre la langue plus facile à utiliser, d'enseigner, d'expliquer, avec moins de cas de bord ou de pièges. Je pense donc qu'il serait utile de l'effort supplémentaire. Quelques gars de Redmond gratter la tête pendant une quinzaine de jours, et comme un résultat des millions de programmeurs au cours de la prochaine décennie peut se détendre un peu plus.

(J'ai aussi le port d'un sordide désir pour qu'il y ait un moyen de rendre l' yield return lever une exception qui a été fourré dans la machine d'état "de l'extérieur", par le code de conduite de l'itération. Mais mes raisons pour vouloir ce sont assez obscures.)

Fait une requête que j'ai à propos de Jon réponse est à voir avec le taux de retour de l'expression de lancer.

Bien évidemment le rendement de retour de 10 n'est pas si mal. Mais ce serait une mauvaise chose:

yield return File.ReadAllText("c:\\missing.txt").Length;

Donc ne serait-il pas plus logique de les évaluer à l'intérieur du précédent bloc try/catch:

case just_before_try_state:
    try
    {
        Console.WriteLine("a");
        __current = File.ReadAllText("c:\\missing.txt").Length;
    }
    catch (Something e)
    {
        CatchBlock();
        goto case post;
    }
    return true;

Le prochain problème pourrait être imbriqués les blocs try/catch et relancés exceptions:

try
{
    Console.WriteLine("x");

    try
    {
        Console.WriteLine("a");
        yield return 10;
        Console.WriteLine("b");
    }
    catch (Something e)
    {
        Console.WriteLine("y");

        if ((DateTime.Now.Second % 2) == 0)
            throw;
    }
}
catch (Something e)
{
    Console.WriteLine("Catch block");
}
Console.WriteLine("Post");

Mais je suis sûr que c'est possible...

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