Qu'est-ce que la «rupture de rendement»; faire en C #?

494

J'ai vu cette syntaxe dans MSDN:, yield breakmais je ne sais pas ce qu'elle fait. Est-ce que quelqu'un sait?

skb
la source
26
Pourtant, la documentation MSDN le mentionne mais ne l'explique pas. C'est une mauvaise façon de taquiner un développeur avide de connaissances.
Phil
3
Le retour de rendement élimine le besoin d'une liste de sauvegarde, c'est-à-dire que vous n'avez pas besoin de coder quelque chose comme MyList.Add(...)simplement le faire yield return .... Si vous devez sortir prématurément de la boucle et renvoyer la liste de sauvegarde virtuelle que vous utilisezyield break;
The Muffin Man

Réponses:

529

Il spécifie qu'un itérateur a pris fin. Vous pouvez penser yield breakà une returndéclaration qui ne renvoie pas de valeur.

Par exemple, si vous définissez une fonction comme un itérateur, le corps de la fonction peut ressembler à ceci:

for (int i = 0; i < 5; i++)
{
    yield return i;
}

Console.Out.WriteLine("You will see me");

Notez qu'après que la boucle a terminé tous ses cycles, la dernière ligne est exécutée et vous verrez le message dans votre application console.

Ou comme ça avec yield break:

int i = 0;
while (true)
{
    if (i < 5)
    {
        yield return i;
    }
    else
    {
        // note that i++ will not be executed after this
        yield break;
    }
    i++;
}

Console.Out.WriteLine("Won't see me");

Dans ce cas, la dernière instruction n'est jamais exécutée car nous avons quitté la fonction plus tôt.

Damir Zekić
la source
8
Serait-ce simplement breakau lieu de yield breakdans votre exemple ci-dessus? Le compilateur ne s'en plaint pas.
orad
85
@orad un simple breakdans ce cas arrêterait la boucle, mais il n'interromprait pas l'exécution de la méthode, ainsi la dernière ligne serait exécutée et le "Texte ne me verra pas" serait réellement vu.
Damir Zekić
5
@Damir Zekić Pourriez-vous également ajouter à votre réponse pourquoi vous devriez préférer la rupture de rendement au rendement et quelles sont les différences entre les deux?
Bruno Costa
11
@ DamirZekić retournant null et la rupture de rendement n'est pas la même. Si vous retournez null, vous pouvez obtenir un NullReferenceExceptionénumérateur de IEnumerable, alors que, avec yield break, vous ne le faites pas (il existe une instance sans élément).
Bruno Costa
6
@BrunoCosta Essayer d'avoir des retours réguliers dans la même méthode donne une erreur de compilation. En utilisant les yield return xalertes du compilateur, vous voulez que cette méthode soit du sucre syntaxique pour créer un Enumeratorobjet. Cet énumérateur a une méthode MoveNext()et une propriété Current. MoveNext () exécute la méthode jusqu'à une yield returninstruction et transforme cette valeur en Current. La prochaine fois que MoveNext est appelé, l'exécution se poursuit à partir de là. yield breakdéfinit Current sur null, signalant la fin de cet énumérateur, de sorte que foreach (var x in myEnum())se termine.
Wolfzoon
57

Termine un bloc itérateur (par exemple, dit qu'il n'y a plus d'éléments dans l'IEnumerable).

Brian
la source
2
avec [+1] - bien qu'académiquement il n'y ait pas d'itérateurs dans .NET. seulement les énumérateurs (une direction, en avant.)
Shaun Wilson
@Shaun Wilson, mais avec un yieldmot-clé, vous pouvez parcourir la collection dans les deux sens, en outre, vous pouvez prendre tous les éléments de collection suivants non consécutifs
monstr
@monstr vous pouvez itérer la collection de n'importe quelle façon et vous n'avez pas besoin yieldde le faire. je ne dis pas que vous vous trompez, je vois ce que vous proposez; mais, académiquement, il n'y a pas d'itérateurs dans .NET, seulement des énumérateurs (une direction, en avant) - contrairement à stdc ++, il n'y a pas de "cadre d'itérateur générique" défini dans le CTS / CLR. LINQ aide à combler l'écart avec les méthodes d'extension qui utilisent yield return et également les méthodes de rappel , mais ce sont des méthodes d'extension de première classe, pas des itérateurs de première classe. le résultat IEnumerablene peut pas itérer lui-même dans une autre direction que vers l'avant par rapport à l'appelant.
Shaun Wilson
29

Dit à l'itérateur qu'il est arrivé à la fin.

Par exemple:

public interface INode
{
    IEnumerable<Node> GetChildren();
}

public class NodeWithTenChildren : INode
{
    private Node[] m_children = new Node[10];

    public IEnumerable<Node> GetChildren()
    {
        for( int n = 0; n < 10; ++n )
        {
            yield return m_children[ n ];
        }
    }
}

public class NodeWithNoChildren : INode
{
    public IEnumerable<Node> GetChildren()
    {
        yield break;
    }
}
Prendre au piège
la source
21

yieldfait en sorte qu'une IEnumerable<T>méthode se comporte de manière similaire à un thread planifié en coopération (par opposition à préventivement).

yield returnest comme un thread appelant une fonction "Schedule" ou "Sleep" pour abandonner le contrôle du CPU. Tout comme un thread, la IEnumerable<T>méthode récupère les contrôles au point immédiatement après, toutes les variables locales ayant les mêmes valeurs qu'avant le abandon du contrôle.

yield break est comme un fil qui atteint la fin de sa fonction et se termine.

Les gens parlent d'une "machine d'état", mais une machine d'état n'est qu'un "fil". Un thread a un certain état (c'est-à-dire des valeurs de variables locales), et chaque fois qu'il est planifié, il prend une ou plusieurs actions afin d'atteindre un nouvel état. Le point clé à ce sujet yieldest que, contrairement aux threads du système d'exploitation auxquels nous sommes habitués, le code qui l'utilise est figé dans le temps jusqu'à ce que l'itération soit avancée ou terminée manuellement.


la source
9

L' yield breakinstruction arrête l'énumération. En effet, yield breaktermine l'énumération sans retourner aucun élément supplémentaire.

Considérez qu'il existe en fait deux façons dont une méthode d'itérateur peut arrêter l'itération. Dans un cas, la logique de la méthode pourrait naturellement quitter la méthode après avoir renvoyé tous les éléments. Voici un exemple:

IEnumerable<uint> FindPrimes(uint startAt, uint maxCount)
{
    for (var i = 0UL; i < maxCount; i++)
    {
        startAt = NextPrime(startAt);
        yield return startAt;
    }

    Debug.WriteLine("All the primes were found.");
}

Dans l'exemple ci-dessus, la méthode itérateur arrêtera naturellement de s'exécuter une fois que les maxCountnombres premiers auront été trouvés.

L' yield breakinstruction est une autre façon pour l'itérateur de cesser d'énumérer. C'est un moyen de sortir tôt du dénombrement. Voici la même méthode que ci-dessus. Cette fois, la méthode a une limite sur la durée d'exécution de la méthode.

IEnumerable<uint> FindPrimes(uint startAt, uint maxCount, int maxMinutes)
{
    var sw = System.Diagnostics.Stopwatch.StartNew();
    for (var i = 0UL; i < maxCount; i++)
    {
        startAt = NextPrime(startAt);
        yield return startAt;

        if (sw.Elapsed.TotalMinutes > maxMinutes)
            yield break;
    }

    Debug.WriteLine("All the primes were found.");
}

Remarquez l'appel à yield break. En effet, il quitte précocement l'énumération.

Remarquez également que les yield breaktravaux diffèrent d'une simple plaine break. Dans l'exemple ci-dessus, yield breakquitte la méthode sans appeler Debug.WriteLine(..).

Wallace Kelly
la source
7

Ici http://www.alteridem.net/2007/08/22/the-yield-statement-in-c/ est un très bon exemple:

public statique IEnumerable <int> Plage (int min, int max)
{
   tandis que (vrai)
   {
      si (min> = max)
      {
         rupture de rendement;
      }
      rendement retour min ++;
   }
}

et explication, que si une yield breakinstruction est frappée dans une méthode, l'exécution de cette méthode s'arrête sans retour. Il y a des situations temporelles, lorsque vous ne voulez pas donner de résultat, vous pouvez utiliser la rupture de rendement.

Towhid
la source
5

la rupture de rendement n'est qu'un moyen de dire retour pour la dernière fois et ne renvoie aucune valeur

par exemple

// returns 1,2,3,4,5
IEnumerable<int> CountToFive()
{
    yield return 1;
    yield return 2;
    yield return 3;
    yield return 4;
    yield return 5;
    yield break;
    yield return 6;
    yield return 7;
    yield return 8;
    yield return 9;
 }
John ClearZ
la source
4

Si ce que vous entendez par «qu'est-ce que la rupture de rendement fait vraiment», c'est «comment ça marche» - Voir le blog de Raymond Chen pour plus de détails https://devblogs.microsoft.com/oldnewthing/20080812-00/?p=21273

Les itérateurs C # génèrent du code très compliqué.

Tony Lee
la source
12
Eh bien, ils ne sont compliqués que si vous vous souciez du code dans lequel ils sont compilés. Le code qui les utilise est assez simple.
Robert Rossney,
@Robert, c'est ce que je voulais dire, donc j'ai mis à jour la réponse pour inclure votre commentaire
Tony Lee
-2

Le mot clé yield est utilisé avec le mot clé return pour fournir une valeur à l'objet énumérateur. yield return spécifie la ou les valeurs renvoyées. Lorsque la déclaration de rendement est atteinte, l'emplacement actuel est stocké. L'exécution est redémarrée à partir de cet emplacement lors du prochain appel de l'itérateur.

Pour expliquer la signification à l'aide d'un exemple:

    public IEnumerable<int> SampleNumbers()
    {
        int counter = 0;
        yield return counter;

        counter = counter + 2;

        yield return counter;

        counter = counter + 3;

        yield return counter ;
    }

Les valeurs renvoyées lors de cette itération sont: 0, 2, 5.

Il est important de noter que la variable de compteur dans cet exemple est une variable locale. Après la deuxième itération qui retourne la valeur de 2, la troisième itération commence là où elle était avant, tout en conservant la valeur précédente de la variable locale nommée counter qui était 2.

Eranga Dissanayaka
la source
11
Vous n'avez pas expliqué ce que yield breakfait
Jay Sullivan
Je ne pense pas qu'il yield returnsupporte réellement le retour de plusieurs valeurs. Ce n'est peut-être pas ce que vous vouliez dire, mais c'est ainsi que je l'ai lu.
Sam
Sam - la méthode SampleNumbers avec plusieurs instructions return return fonctionne en fait, la valeur de l'itérateur est retournée immédiatement et l'exécution reprend lorsque la valeur suivante est demandée. J'ai vu des gens mettre fin à une méthode comme celle-ci avec une "rupture de rendement", mais ce n'est pas nécessaire. Atteindre la fin de la méthode met également fin à l'itérateur
Loren Paulsen
la raison pour laquelle il s'agit d'un mauvais exemple yield breakest qu'il ne contient pas d'énumérateur au niveau de la langue tel qu'un foreach- lorsque vous utilisez un énumérateur, la yield breakvaleur réelle est fournie. cet exemple ressemble à une boucle déroulée. vous ne verrez presque jamais ce code dans le monde réel (nous pouvons tous penser à certains cas extrêmes, bien sûr) également, il n'y a pas d'itérateur ici. le "bloc itérateur" ne peut pas s'étendre au-delà de la méthode pour des raisons de spécification de langage. ce qui est réellement retourné est un "énumérable", voir aussi: stackoverflow.com/questions/742497/…
Shaun Wilson