web-dev-qa-db-fra.com

Pourquoi utiliser le mot-clé yield, alors que je pourrais simplement utiliser un IEnumerable ordinaire?

Compte tenu de ce code:

IEnumerable<object> FilteredList()
{
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            yield return item;
    }
}

Pourquoi ne devrais-je pas simplement le coder de cette façon?:

IEnumerable<object> FilteredList()
{
    var list = new List<object>(); 
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            list.Add(item);
    }
    return list;
}

Je comprends en quelque sorte ce que fait le mot clé yield. Il dit au compilateur de construire un certain type de chose (un itérateur). Mais pourquoi l'utiliser? En plus d'être un peu moins de code, qu'est-ce que ça fait pour moi?

171
James P. Wright

L'utilisation de yield rend la collection paresseuse.

Disons que vous avez juste besoin des cinq premiers éléments. À votre façon, je dois parcourir la liste entière pour obtenir les cinq premiers éléments. Avec yield, je ne fais que parcourir les cinq premiers éléments.

239
Robert Harvey

L'avantage des blocs d'itérateur est qu'ils fonctionnent paresseusement. Vous pouvez donc écrire une méthode de filtrage comme celle-ci:

public static IEnumerable<T> Where<T>(this IEnumerable<T> source,
                                   Func<T, bool> predicate)
{
    foreach (var item in source)
    {
        if (predicate(item))
        {
            yield return item;
        }
    }
}

Cela vous permettra de filtrer un flux aussi longtemps que vous le souhaitez, sans jamais mettre en mémoire tampon plus d'un seul élément à la fois. Si vous n'avez besoin que de la première valeur de la séquence retournée, par exemple, pourquoi voudriez-vous tout copier dans une nouvelle liste?

Comme autre exemple, vous pouvez facilement créer un flux infini à l'aide de blocs d'itérateur. Par exemple, voici une séquence de nombres aléatoires:

public static IEnumerable<int> RandomSequence(int minInclusive, int maxExclusive)
{
    Random rng = new Random();
    while (true)
    {
        yield return rng.Next(minInclusive, maxExclusive);
    }
}

Comment stockeriez-vous une séquence infinie dans une liste?

Mon série de blogs Edulinq donne un exemple d'implémentation de LINQ to Objects qui rend lourd l'utilisation des blocs itérateurs. LINQ est fondamentalement paresseux où il peut être - et mettre les choses dans une liste ne fonctionne tout simplement pas de cette façon.

127
Jon Skeet

Avec le code "liste", vous devez traiter la liste complète avant de pouvoir la passer à l'étape suivante. La version "yield" passe immédiatement l'élément traité à l'étape suivante. Si cette "étape suivante" contient un ".Take (10)" alors la version "yield" ne traitera que les 10 premiers éléments et oubliera le reste. Le code "liste" aurait tout traité.

Cela signifie que vous voyez le plus de différence lorsque vous devez effectuer un grand nombre de traitements et/ou avoir de longues listes d'éléments à traiter.

42
Hans Kesting

Vous pouvez utiliser yield pour renvoyer des éléments qui ne figurent pas dans une liste. Voici un petit échantillon qui pourrait parcourir indéfiniment une liste jusqu'à son annulation.

public IEnumerable<int> GetNextNumber()
{
    while (true)
    {
        for (int i = 0; i < 10; i++)
        {
            yield return i;
        }
    }
}

public bool Canceled { get; set; }

public void StartCounting()
{
    foreach (var number in GetNextNumber())
    {
        if (this.Canceled) break;
        Console.WriteLine(number);
    }
}

Cela écrit

0
1
2
3
4
5
6
7
8
9
0
1
2
3
4

...etc. à la console jusqu'à annulation.

23
Jason Whitted
object jamesItem = null;
foreach(var item in FilteredList())
{
   if (item.Name == "James")
   {
       jamesItem = item;
       break;
   }
}
return jamesItem;

Lorsque le code ci-dessus est utilisé pour parcourir FilteredList () et en supposant que item.Name == "James" sera satisfait sur le 2ème élément de la liste, la méthode utilisant yield donnera deux fois. C'est un comportement paresseux.

Alors que la méthode utilisant list ajoutera tous les n objets à la liste et passera la liste complète à la méthode appelante.

C'est exactement un cas d'utilisation où la différence entre IEnumerable et IList peut être mise en évidence.

10
humblelistener

Le meilleur exemple du monde réel que j'ai vu pour l'utilisation de yield serait de calculer une séquence de Fibonacci.

Considérez le code suivant:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(string.Join(", ", Fibonacci().Take(10)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(15).Take(1)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(10).Take(5)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(100).Take(1)));
        Console.ReadKey();
    }

    private static IEnumerable<long> Fibonacci()
    {
        long a = 0;
        long b = 1;

        while (true)
        {
            long temp = a;
            a = b;

            yield return a;

            b = temp + b;
        }
    }
}

Cela reviendra:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55
987
89, 144, 233, 377, 610
1298777728820984005

C'est bien car cela vous permet de calculer rapidement et facilement une série infinie, vous donnant la possibilité d'utiliser les extensions Linq et de rechercher uniquement ce dont vous avez besoin.

7
Middas

pourquoi utiliser [rendement]? En plus d'être un peu moins de code, qu'est-ce que ça fait pour moi?

Parfois, c'est utile, parfois non. Si l'ensemble des données doit être examiné et renvoyé, il n'y aura aucun avantage à utiliser le rendement, car il n'a fait qu'introduire des frais généraux.

Quand le rendement brille vraiment, c'est quand seul un ensemble partiel est retourné. Je pense que le meilleur exemple est le tri. Supposons que vous ayez une liste d'objets contenant une date et un montant en dollars de cette année et que vous aimeriez voir les premiers (5) enregistrements de l'année.

Pour ce faire, la liste doit être triée par date, puis les 5 premières prises. Si cela se faisait sans rendement, la liste entière devrait être triée, jusqu'à ce que les deux dernières dates soient en ordre.

Cependant, avec le rendement, une fois les 5 premiers éléments établis, le tri s'arrête et les résultats sont disponibles. Cela peut vous faire gagner beaucoup de temps.

1
Travis J

La déclaration de rendement vous permet de renvoyer un seul article à la fois. Vous collectez tous les éléments d'une liste et renvoyez à nouveau cette liste, qui est une surcharge de mémoire.

0
Prabhavith