Question

Dans le Comment puis-je exposer uniquement un fragment de IList<> la première question contenait l'extrait de code suivant :

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

Que fait le mot-clé rendement là-bas ?Je l'ai vu référencé à plusieurs endroits et une autre question, mais je n'ai pas vraiment compris ce qu'il fait réellement.J'ai l'habitude de penser au rendement dans le sens où un fil cède à un autre, mais cela ne semble pas pertinent ici.

Était-ce utile?

La solution

Le yield le mot-clé fait en fait beaucoup ici.

La fonction renvoie un objet qui implémente le IEnumerable<object> interface.Si une fonction appelante démarre foreachEn passant sur cet objet, la fonction est appelée à nouveau jusqu'à ce qu'elle « cède ».C'est du sucre syntaxique introduit dans C#2.0.Dans les versions précédentes, vous deviez créer le vôtre IEnumerable et IEnumerator objets pour faire des choses comme ça.

Le moyen le plus simple de comprendre un code comme celui-ci est de saisir un exemple, de définir des points d'arrêt et de voir ce qui se passe.Essayez de suivre cet exemple :

public void Consumer()
{
    foreach(int i in Integers())
    {
        Console.WriteLine(i.ToString());
    }
}

public IEnumerable<int> Integers()
{
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 16777216;
}

En parcourant l'exemple, vous trouverez le premier appel à Integers() Retour 1.Le deuxième appel revient 2 et la ligne yield return 1 n'est pas exécuté à nouveau.

Voici un exemple concret :

public IEnumerable<T> Read<T>(string sql, Func<IDataReader, T> make, params object[] parms)
{
    using (var connection = CreateConnection())
    {
        using (var command = CreateCommand(CommandType.Text, sql, connection, parms))
        {
            command.CommandTimeout = dataBaseSettings.ReadCommandTimeout;
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return make(reader);
                }
            }
        }
    }
}

Autres conseils

Itération.Il crée une machine d'état "sous les couvertures" qui se souvient où vous étiez à chaque cycle supplémentaire de la fonction et reprend à partir de là.

Le rendement a deux grandes utilisations,

  1. Cela permet de fournir une itération personnalisée sans créer de collections temporaires.

  2. Cela aide à faire une itération avec état.enter image description here

Afin d'expliquer ci-dessus deux points de manière plus démonstrative, j'ai créé une vidéo simple que vous pouvez regarder ici

Récemment, Raymond Chen a également publié une intéressante série d'articles sur le mot-clé rendement.

Bien qu'il soit nominalement utilisé pour implémenter facilement un modèle d'itérateur, il peut être généralisé dans une machine à états.Inutile de citer Raymond, la dernière partie renvoie également à d'autres utilisations (mais l'exemple du blog d'Entin est particulièrement bon, montrant comment écrire du code sécurisé asynchrone).

À première vue, le rendement est un sucre .NET pour renvoyer un IEnumerable.

Sans rendement, tous les éléments de la collection sont créés d'un coup :

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        return new List<SomeData> {
            new SomeData(), 
            new SomeData(), 
            new SomeData()
        };
    }
}

Même code en utilisant rendement, il renvoie élément par élément :

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        yield return new SomeData();
        yield return new SomeData();
        yield return new SomeData();
    }
}

L'avantage d'utiliser le rendement est que si la fonction consommant vos données a simplement besoin du premier élément de la collection, le reste des éléments ne sera pas créé.

L'opérateur de rendement permet la création d'éléments au fur et à mesure de la demande.C'est une bonne raison de l'utiliser.

yield return est utilisé avec les enquêteurs.À chaque appel de l'instruction Yield, le contrôle est rendu à l'appelant mais il garantit que l'état de l'appelé est maintenu.Pour cette raison, lorsque l'appelant énumère l'élément suivant, il continue l'exécution dans la méthode appelée à partir de l'instruction immédiatement après l'élément suivant. yield déclaration.

Essayons de comprendre cela avec un exemple.Dans cet exemple, correspondant à chaque ligne, j'ai mentionné l'ordre dans lequel se déroule l'exécution.

static void Main(string[] args)
{
    foreach (int fib in Fibs(6))//1, 5
    {
        Console.WriteLine(fib + " ");//4, 10
    }            
}

static IEnumerable<int> Fibs(int fibCount)
{
    for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
    {
        yield return prevFib;//3, 9
        int newFib = prevFib + currFib;//6
        prevFib = currFib;//7
        currFib = newFib;//8
    }
}

De plus, l'état est conservé pour chaque énumération.Supposons que j'ai un autre appel pour Fibs() méthode, l'état sera réinitialisé pour cela.

Intuitivement, le mot-clé renvoie une valeur de la fonction sans la quitter, c'est-à-diredans votre exemple de code, il renvoie le courant item valeur puis reprend la boucle.Plus formellement, il est utilisé par le compilateur pour générer du code pour un itérateur.Les itérateurs sont des fonctions qui renvoient IEnumerable objets.Le MSDN a plusieurs des articles à propos d'eux.

Une implémentation de liste ou de tableau charge tous les éléments immédiatement tandis que l'implémentation de rendement fournit une solution d'exécution différée.

En pratique, il est souvent souhaitable d’effectuer le minimum de travail nécessaire afin de réduire la consommation de ressources d’une application.

Par exemple, nous pouvons avoir une application qui traite des millions d’enregistrements à partir d’une base de données.Les avantages suivants peuvent être obtenus lorsque nous utilisons IEnumerable dans un modèle basé sur l'exécution différée :

  • Évolutivité, fiabilité et prévisibilité sont susceptibles de s’améliorer puisque le nombre d’enregistrements n’affecte pas de manière significative les besoins en ressources de l’application.
  • Performance et réactivité sont susceptibles de s'améliorer puisque le traitement peut démarrer immédiatement au lieu d'attendre que la collection entière soit chargée en premier.
  • Récupérabilité et utilisation sont susceptibles de s'améliorer puisque l'application peut être arrêtée, démarrée, interrompue ou échouer.Seuls les éléments en cours seront perdus par rapport à la pré-récupération de toutes les données où seule une partie des résultats a été réellement utilisée.
  • Traitement continu est possible dans des environnements où des flux de charge de travail constants sont ajoutés.

Voici une comparaison entre la création d'abord d'une collection telle qu'une liste et l'utilisation de rendement.

Exemple de liste

    public class ContactListStore : IStore<ContactModel>
    {
        public IEnumerable<ContactModel> GetEnumerator()
        {
            var contacts = new List<ContactModel>();
            Console.WriteLine("ContactListStore: Creating contact 1");
            contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
            Console.WriteLine("ContactListStore: Creating contact 2");
            contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
            Console.WriteLine("ContactListStore: Creating contact 3");
            contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
            return contacts;
        }
    }

    static void Main(string[] args)
    {
        var store = new ContactListStore();
        var contacts = store.GetEnumerator();

        Console.WriteLine("Ready to iterate through the collection.");
        Console.ReadLine();
    }

Sortie console
ContactListStore :Créer un contact 1
ContactListStore :Création de contact 2
ContactListStore :Création de contact 3
Prêt à parcourir la collection.

Note:La collection entière a été chargée en mémoire sans même demander un seul élément dans la liste

Exemple de rendement

public class ContactYieldStore : IStore<ContactModel>
{
    public IEnumerable<ContactModel> GetEnumerator()
    {
        Console.WriteLine("ContactYieldStore: Creating contact 1");
        yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
        Console.WriteLine("ContactYieldStore: Creating contact 2");
        yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
        Console.WriteLine("ContactYieldStore: Creating contact 3");
        yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
    }
}

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();

    Console.WriteLine("Ready to iterate through the collection.");
    Console.ReadLine();
}

Sortie console
Prêt à parcourir la collection.

Note:La collection n'a pas été exécutée du tout.Cela est dû à la nature « d’exécution différée » de IEnumerable.La construction d’un objet n’aura lieu que lorsque cela sera réellement nécessaire.

Appelons à nouveau la collection et observons le comportement lorsque nous récupérons le premier contact de la collection.

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();
    Console.WriteLine("Ready to iterate through the collection");
    Console.WriteLine("Hello {0}", contacts.First().FirstName);
    Console.ReadLine();
}

Sortie console
Prêt à parcourir la collection
ContacterYieldStore :Créer un contact 1
Bonjour bob

Bon!Seul le premier contact a été construit lorsque le client a « retiré » l'objet de la collection.

Voici une manière simple de comprendre le concept :L'idée de base est que si vous voulez une collection que vous pouvez utiliser "foreach" activé, mais rassembler les éléments dans la collection coûte cher pour une raison quelconque (comme les interroger dans une base de données), ET vous n'aurez souvent pas besoin de la collection entière, vous créez alors une fonction qui construit la collection un élément à la fois et le restitue au consommateur (qui peut alors mettre fin à l'effort de collecte plus tôt).

Pense-y de cette façon: Vous allez au comptoir des viandes et souhaitez acheter une livre de jambon tranché.Le boucher prend un jambon de 10 livres à l'arrière, le met sur la trancheuse, coupe le tout en tranches, puis vous ramène le tas de tranches et en mesure une livre.(ANCIENNE manière).Avec yield, le boucher amène la trancheuse au comptoir et commence à trancher et à « céder » chaque tranche sur la balance jusqu'à ce qu'elle mesure 1 livre, puis l'enveloppe pour vous et vous avez terminé. L'ancienne méthode est peut-être meilleure pour le boucher (lui permet d'organiser ses machines comme il l'entend), mais la nouvelle méthode est clairement plus efficace dans la plupart des cas pour le consommateur.

Le yield mot-clé vous permet de créer un IEnumerable<T> sous la forme d'un bloc itérateur.Ce bloc itérateur prend en charge exécution différée et si vous n’êtes pas familier avec le concept, cela peut paraître presque magique.Cependant, en fin de compte, il s’agit simplement de code qui s’exécute sans astuces étranges.

Un bloc itérateur peut être décrit comme du sucre syntaxique dans lequel le compilateur génère une machine à états qui suit la progression de l'énumération de l'énumérable.Pour énumérer un énumérable, vous utilisez souvent un foreach boucle.Cependant, un foreach la boucle est également du sucre syntaxique.Vous êtes donc deux abstractions éloignées du code réel, c'est pourquoi il peut être difficile au début de comprendre comment tout cela fonctionne ensemble.

Supposons que vous disposiez d’un bloc itérateur très simple :

IEnumerable<int> IteratorBlock()
{
    Console.WriteLine("Begin");
    yield return 1;
    Console.WriteLine("After 1");
    yield return 2;
    Console.WriteLine("After 2");
    yield return 42;
    Console.WriteLine("End");
}

Les vrais blocs d'itérateurs ont souvent des conditions et des boucles, mais lorsque vous vérifiez les conditions et déroulez les boucles, elles finissent toujours par yield instructions entrelacées avec un autre code.

Pour énumérer le bloc itérateur a foreach la boucle est utilisée :

foreach (var i in IteratorBlock())
    Console.WriteLine(i);

Voici le résultat (pas de surprise ici) :

Begin
1
After 1
2
After 2
42
End

Comme indiqué ci-dessus foreach est du sucre syntaxique :

IEnumerator<int> enumerator = null;
try
{
    enumerator = IteratorBlock().GetEnumerator();
    while (enumerator.MoveNext())
    {
        var i = enumerator.Current;
        Console.WriteLine(i);
    }
}
finally
{
    enumerator?.Dispose();
}

Pour tenter de démêler cela, j'ai créé un diagramme de séquence avec les abstractions supprimées :

C# iterator block sequence diagram

La machine à états générée par le compilateur implémente également l'énumérateur mais pour rendre le diagramme plus clair, je les ai montrés comme des instances distinctes.(Lorsque la machine à états est énumérée à partir d'un autre thread, vous obtenez en fait des instances distinctes, mais ce détail n'est pas important ici.)

Chaque fois que vous appelez votre bloc itérateur, une nouvelle instance de la machine à états est créée.Cependant, aucun de votre code dans le bloc itérateur n'est exécuté jusqu'à ce que enumerator.MoveNext() s'exécute pour la première fois.C’est ainsi que fonctionne l’exécution différée.Voici un exemple (plutôt idiot) :

var evenNumbers = IteratorBlock().Where(i => i%2 == 0);

À ce stade, l'itérateur ne s'est pas exécuté.Le Where la clause crée un nouveau IEnumerable<T> qui enveloppe le IEnumerable<T> renvoyé par IteratorBlock mais cet énumérable n'a pas encore été énuméré.Cela se produit lorsque vous exécutez un foreach boucle:

foreach (var evenNumber in evenNumbers)
    Console.WriteLine(eventNumber);

Si vous énumérez l'énumérable deux fois, une nouvelle instance de la machine à états est créée à chaque fois et votre bloc itérateur exécutera le même code deux fois.

Notez que les méthodes LINQ comme ToList(), ToArray(), First(), Count() etc.utilisera un foreach boucle pour énumérer l’énumérable.Par exemple ToList() énumérera tous les éléments de l'énumérable et les stockera dans une liste.Vous pouvez maintenant accéder à la liste pour obtenir tous les éléments de l'énumérable sans que le bloc itérateur ne s'exécute à nouveau.Il existe un compromis entre l'utilisation du processeur pour produire les éléments de l'énumération plusieurs fois et la mémoire pour stocker les éléments de l'énumération afin d'y accéder plusieurs fois lors de l'utilisation de méthodes telles que ToList().

Si je comprends bien, voici comment je formulerais cela du point de vue de la fonction implémentant IEnumerable avec rendement.

  • En voici un.
  • Appelez à nouveau si vous en avez besoin d'un autre.
  • Je me souviendrai de ce que je t'ai déjà donné.
  • Je ne saurai si je peux vous en donner un autre que lorsque vous rappellerez.

Le mot-clé C# rendement, pour le dire simplement, permet de nombreux appels à un corps de code, appelé itérateur, qui sait comment revenir avant la fin et, lorsqu'il est à nouveau appelé, continue là où il s'est arrêté - c'est-à-direcela aide un itérateur à devenir de manière transparente avec état pour chaque élément d'une séquence que l'itérateur renvoie lors d'appels successifs.

En JavaScript, le même concept s'appelle Générateurs.

C'est un moyen très simple et facile de créer un énumérable pour votre objet.Le compilateur crée une classe qui encapsule votre méthode et qui implémente, dans ce cas, IEnumerable<object>.Sans le mot clé rendement, vous devrez créer un objet qui implémente IEnumerable<object>.

Cela produit une séquence énumérable.Ce qu'il fait, c'est créer une séquence IEnumerable locale et la renvoyer comme résultat de méthode

Ce lien a un exemple simple

Des exemples encore plus simples sont ici

public static IEnumerable<int> testYieldb()
{
    for(int i=0;i<3;i++) yield return 4;
}

Notez que le rendement du rendement ne sera pas renvoyé par la méthode.Vous pouvez même mettre un WriteLine après le yield return

Ce qui précède produit un IEnumerable de 4 entiers 4,4,4,4

Ici avec un WriteLine.Ajoutera 4 à la liste, imprimera abc, puis ajoutera 4 à la liste, puis terminera la méthode et reviendra ainsi réellement de la méthode (une fois la méthode terminée, comme cela se produirait avec une procédure sans retour).Mais cela aurait une valeur, un IEnumerable liste de ints, qu'il revient une fois terminé.

public static IEnumerable<int> testYieldb()
{
    yield return 4;
    console.WriteLine("abc");
    yield return 4;
}

Notez également que lorsque vous utilisez rendement, ce que vous renvoyez n'est pas du même type que la fonction.C'est du type d'un élément à l'intérieur du IEnumerable liste.

Vous utilisez le rendement avec le type de retour de la méthode comme IEnumerable.Si le type de retour de la méthode est int ou List<int> et tu utilises yield, alors il ne sera pas compilé.Vous pouvez utiliser IEnumerable type de retour de méthode sans rendement mais il semble que vous ne puissiez peut-être pas utiliser le rendement sans IEnumerable type de retour de méthode.

Et pour qu'il s'exécute, vous devez l'appeler d'une manière spéciale.

static void Main(string[] args)
{
    testA();
    Console.Write("try again. the above won't execute any of the function!\n");

    foreach (var x in testA()) { }


    Console.ReadLine();
}



// static List<int> testA()
static IEnumerable<int> testA()
{
    Console.WriteLine("asdfa");
    yield return 1;
    Console.WriteLine("asdf");
}

Il essaie d'apporter un peu de Ruby Goodness :)
Concept: Ceci est un exemple de code Ruby qui imprime chaque élément du tableau

 rubyArray = [1,2,3,4,5,6,7,8,9,10]
    rubyArray.each{|x| 
        puts x   # do whatever with x
    }

Implémentation de chaque méthode du tableau rendements contrôle sur l'appelant (le 'puts x') avec chaque élément du tableau soigneusement présenté comme x.L'appelant peut alors faire tout ce qu'il doit faire avec x.

Cependant .Filet ça ne va pas jusqu'au bout ici..C# semble avoir couplé rendement avec IEnumerable, vous obligeant en quelque sorte à écrire une boucle foreach dans l'appelant, comme le montre la réponse de Mendelt.Un peu moins élégant.

//calling code
foreach(int i in obCustomClass.Each())
{
    Console.WriteLine(i.ToString());
}

// CustomClass implementation
private int[] data = {1,2,3,4,5,6,7,8,9,10};
public IEnumerable<int> Each()
{
   for(int iLooper=0; iLooper<data.Length; ++iLooper)
        yield return data[iLooper]; 
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top