IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Framework .NET Discussion :

usage ou non usage du mot cle YIELD dans une boucle foreach


Sujet :

Framework .NET

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éprouvé
    Profil pro
    Inscrit en
    Mai 2002
    Messages
    988
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2002
    Messages : 988
    Par défaut usage ou non usage du mot cle YIELD dans une boucle foreach
    Bonjour,

    La question que j'ai aujourd'hui concerne la différence entre l'usage du moit clé YIELD dans une boucle foreach et son non usage dans ce même type de boucle.


    D'après ce que je connais , chaque interface IEnumerable<T>, doit définir une méthode GetEnumerator()
    Cette méthode retourne un objet qui implémente l'interface IEnumerator<T>.

    Cet objet retourné fournit la logique dont a besoin l'instruction foreach.

    Cette instruction foreach fournit donc l'énumérateur par défaut dont on a besoin pour le parcours d'une collection.

    voici un exemple de boucle foreach


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
     
    class CustomCollectionClass<T> : IEnumerable<T>{
     
    public IEnumerator<T>GetEnumerator(){
    ......
    }
     
    CustomCollectionCalss<int> intCollection= new CustomCollectionClass<int>();
    intCollection.Add(3);
    intCollection.Add(5);
    intCollection.Add(8);
    intCollection.Add(2);
    //la boucle foreach entraîne l'usage implicite de  l'enumerateur par défaut
    foreach (int temp in intCollection){
     
    }
     
    }
    Je peux donc considérer qu'à chaque fois que j'utilise la boucle foreach, j'utilise implicitement un objet de type IEnumerator qui contient les méthodes MoveNext et Reset ainsi que la property Current.

    Par ailleurs, j'apprends que si j'utilise mot clé yield dans une boucle for, ainsi

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
     
    class BasicCollection<T>:IEnumerable<T>{
     
    private List<T> data = new List<T>;
     
    public void FillList(params T[]items){
     
    foreach(var datum in items)
    data.Add(datum);
     
    }
     
    IEnumerator<T>IEnumerable<T>.GetEnumerator(){
    foreach (var datum in data){
     
    yield return datum;
     
     
    }
     
    }
    Dans ce cas, la méthode GetEnumerator()ne retourne pas un objet de type IEnumerator<T> ,c'est à dire qui implémente les méthodes MoveNext et Reset ainsi que retourne la property Current(Ah bon!) .
    A la place elle boucle au niveau des éléménts de le collection data et retourne chaque élement l'un après l'autre(j'avoue que je ne vois pas la différence avec le fait d'appeler implicitement les méthodes moveNext et utiliser la property Current).

    C'est le mot clé YIELD qui va permettre ce retour d'un objet de type IEnumerator<T>.
    J'avoue que cela contredit ce que j'ai écris précédemment concernant l'usage d'un enumateur par défaut dans une boucle foreach

    En effet,j'apprends que par l'usage du mot clé YIELD :
    -la valeur concernée par chaque itération est renvoyée donc cela implique l'usage implicite de la property Current

    -le passage à la prochaine valeur s'effectue grâce à l'appel implicite de la méthode MoveNext()

    Le mot clé YIELD est utilisé par le compilateur pour générr une implémentation de l'interface IEnumerator<T< qui contient les méthodes MoveNext() et Reset()
    ainsi que la property Current.

    J'avoue que je ne vois pas la différence entre une simple boucle foreach , qui, si j'ai bien compris, utilise implicitement un objet de type IEnumerator<T> et l'usage du mot clé YIELD dans une boucle foreach pour boucler au niveau des éléments d'une collection.

    Je vous remercie beaucoup de bien vouloir m'expliquer la différence exacte entre l'usage et le non usage de ce mot clé dans une boucle foreach.

    Bien cordialement.

    new_wave

  2. #2
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Par défaut
    La façon dont ta question est posée n'a pas beaucoup de sens, sans doute parce que tu t'es un peu emmêlé les pinceaux... Je vais donc y répondre indirectement, en essayant d'expliquer le sens de yield return.

    En fait, le principe d'un itérateur (c'est-à-dire une méthode qui utilise yield return) est que le compilateur va réécrire le code de façon à ce que la méthode renvoie en fait un objet (généré par le compilateur) qui implémente IEnumerator<T> (ou IEnumerable<T>, car on peut déclarer l'un où l'autre comme type de retour d'un itérateur) et renvoie les éléments en suivant la logique de la méthode. C'est juste une facilité d'écriture pour éviter d'avoir à écrire manuellement un objet IEnumerator<T> ou IEnumerable<T>.

    Par exemple, si tu as l'itérateur suivant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    IEnumerable<int> GetNumbers()
    {
        yield return 42;
        yield return 123;
        yield return 999;
    }
    Le compilateur va réécrire ça de la façon suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    IEnumerable<int> GetNumbers()
    {
        return new GetNumbersEnumerable();
    }
    La classe GetNumbersEnumerable (en réalité c'est un nom un peu plus "bizarre" que ça, mais pour l'explication c'est plus simple comme ça) est générée par le compilateur et ressemble à quelque chose comme ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    class GetNumbersEnumerable : IEnumerable<int>
    {
        public IEnumerator<int> GetEnumerator()
        {
            return new GetNumbersEnumerator();
        }
     
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
     
        class GetNumbersEnumerator : IEnumerator<int>
        {
            private int _current;
            private int _state;
            public bool MoveNext()
            {
                switch (_state)
                {
                    case 0:
                        _current = 42;
                        _state++;
                        return true;
                    case 1:
                        _current = 123;
                        _state++;
                        return true;
                    case 2:
                        _current = 999;
                        _state++;
                        return true;
                    default:
                        return false;
                }
            }
     
            public int Current
            {
                get { return _current; }
            }
     
            public void Dispose()
            {
            }
     
            public void Reset()
            {
                _state = 0;
            }
     
            object IEnumerator.Current
            {
                get { return Current; }
            }
        }
    }
    La classe GetNumbersEnumerator implémente en fait dans la méthode MoveNext un automate fini qui reproduit la logique du code de ta méthode GetNumbers d'origine. Ici c'est un cas très simple, donc le code de MoveNext est facilement compréhensible, mais pour des cas plus compliqués ça génère un code assez peu lisible à base de switch et de goto, pour pourvoir reprendre là où ça en était...

    Donc en gros, même si toi, tu ne renvoies pas explicitement un objet IEnumerator<T>, le compilateur s'en charge pour toi

    Plus d'infos sur MSDN : Itérateurs

  3. #3
    Membre éprouvé
    Profil pro
    Inscrit en
    Mai 2002
    Messages
    988
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2002
    Messages : 988
    Par défaut
    Bonjour et merci encore beaucoup de ta réponse.

    Ce que j'en comprends, c'est qu'au lieu d'implémenter par nous mêmes les méthodes MoveNext ()ou Reset() ou encore de récupérer la valeur courante par le getter de la property Current, on utilise une instruction rde type yield return ;

    Ce que je ne comprends pas bien, c'est quelle différence existe-til entre l'usage d'une boucle foreach et l'usage d'une méthode qui contient une instruction return yield.
    En effet, avec la boucle foreach comme avec l'instruction yield return , le compilateur implémente par défaut in itérateur c'est à dire, comme tu me l'as expliqué , implemente une méthode MoveNext, une property current et une méthode Reset().


    Par ailleurs, j'aimerais poser deux questions simples.

    Quand j'utilise une boucle foreach dans une classe, je ne crée pas forcément de méthode GetEnumerator() qui implémente du code qui sera executé à chaque boucle foreach.
    Est-ce du fait que la classe fait appel , par défaut, à une méthode GetEnumerator, qui utilise un itérateur par défaut?



    Pour les classes de collection qui implémentent IEnumerable<T>.

    Si je souhaite parcourir une collection de ce type avec une boucle foreach et en utilisant l'énumérateur par défaut, suis je obligé d'implémenter la méthode GetEnumerator().

    Pour finir, je te remercie beaucoup de confirmer ou d'infirmer ce que j'écris et de bien vouloir me corriger si besoin est.

    Si je ne suis pas obligée d'implémenter la méthode GetEnumerator()par ce que je souhaite utiliser l'itérateur par défaut, je comprends alors qu'on définit nous-mêmes la méthode GetEnumerator et on utilise

    le mot clé yield , si dans une boucle foreach, on ne souhaite pas faire appel à l'énumérateur par défaut.
    L'usage du mot clé YIELD remplace l'appel de la méthode MoveNext(), ainsi que la récupération de la valeur courante d'une collection, par l'appel du getter de la property Current.


    Merci encore beaucoup de ton aide pour m'aider à bien voir clair sur ce sujet.

    Bien cordialement.

    new_wave

  4. #4
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Par défaut
    Citation Envoyé par new_wave Voir le message
    Ce que je ne comprends pas bien, c'est quelle différence existe-til entre l'usage d'une boucle foreach et l'usage d'une méthode qui contient une instruction return yield.
    En effet, avec la boucle foreach comme avec l'instruction yield return , le compilateur implémente par défaut in itérateur c'est à dire, comme tu me l'as expliqué , implemente une méthode MoveNext, une property current et une méthode Reset().
    Ca n'a rien à voir... foreach sert à consommer une séquence, alors que yield return sert à générer une séquence. Bien sûr, rien n'empêche de combiner les deux : tu peux générer une séquence au fur et à mesure que tu en consommes une autre, c'est d'ailleurs un pattern assez fréquent.

    Citation Envoyé par new_wave Voir le message
    Quand j'utilise une boucle foreach dans une classe, je ne crée pas forcément de méthode GetEnumerator() qui implémente du code qui sera executé à chaque boucle foreach.
    Est-ce du fait que la classe fait appel , par défaut, à une méthode GetEnumerator, qui utilise un itérateur par défaut?
    Je ne comprends pas très bien le scénario que tu décris... Pour pouvoir faire foreach (var x in quelquechose), il faut que quelquechose ait une méthode GetEnumerator. Si c'est toi qui écris le code de quelquechose, il faut effectivement que tu implémentes cette méthode, sinon tu te contentes de l'utiliser (implicitement, puisque c'est le compilateur qui s'occupe d'appeler GetEnumerator)


    Citation Envoyé par new_wave Voir le message
    Pour les classes de collection qui implémentent IEnumerable<T>.

    Si je souhaite parcourir une collection de ce type avec une boucle foreach et en utilisant l'énumérateur par défaut, suis je obligé d'implémenter la méthode GetEnumerator().
    Bah si la classe implémente IEnumerable<T>, il y a déjà une méthode GetEnumerator, donc tu n'as pas à l'implémenter.
    Et je ne vois pas trop ce que tu appelles "énumérateur par défaut"...

    Citation Envoyé par new_wave Voir le message
    Si je ne suis pas obligée d'implémenter la méthode GetEnumerator()par ce que je souhaite utiliser l'itérateur par défaut, je comprends alors qu'on définit nous-mêmes la méthode GetEnumerator et on utilise

    le mot clé yield , si dans une boucle foreach, on ne souhaite pas faire appel à l'énumérateur par défaut.
    L'usage du mot clé YIELD remplace l'appel de la méthode MoveNext(), ainsi que la récupération de la valeur courante d'une collection, par l'appel du getter de la property Current.
    Je comprends pas grand chose à ton histoire. L'usage de yield ne "remplace" pas l'appel à MoveNext : au contraire, il crée la méthode MoveNext, selon le mécanisme décrit dans mon précédent message.

    Je répète ce que j'ai dit au début de ce message, parce que je pense que c'est ce point là que tu n'as pas bien compris : foreach sert à consommer une séquence, alors que yield return sert à générer une séquence.

  5. #5
    Membre confirmé
    Inscrit en
    Avril 2010
    Messages
    200
    Détails du profil
    Informations forums :
    Inscription : Avril 2010
    Messages : 200
    Par défaut
    Bonjour,

    Je me joins à la conversation mais pour poser une question simple car au final vous utilisez beaucoup de mots et d'explications compliquées pour simplement expliquer que yield sert à générer une séquence comme tu l'as dit Tomlev.

    Ma question :

    Quelle solution est la mieux à utiliser ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    foreach (string current in list)
    {
        yield return current;
    }
    ou bien
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    List<string> listReturned = new List<string>();
    foreach (string current in list)
    {
        listReturned.Add(current);
    }
    return listReturned;
    ?

    Pour l'avoir utilisé récemment, en deboguant j'avais l'impression qu'avec le yield, ma méthode (contenant le yield) pouvait être appelée plusieurs fois.
    Par exemple une fois lors de l'appel pour récupérer une liste, une autre fois pour faire un simple count et ainsi de suite.

  6. #6
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Par défaut
    Citation Envoyé par Air P-E Voir le message
    Quelle solution est la mieux à utiliser ?
    Ca dépend de ce que tu veux faire, mais en gros, la principale différence est qu'avec yield return, l'exécution est différée. Je veux dire par là qu'au moment où tu appelles la méthode, tu récupères un IEnumerable ou IEnumerator, mais le corps de l'itérateur n'est pas exécuté tant que tu ne commences pas à énumérer le résultat. Et quand tu énumères, la collection source est énumérée au fur et à mesure.

    Alors que si tu remplis une liste et que tu la renvoies, l'énumération est faite immédiatement lors de l'appel de la méthode, et le fait que tu énumère la liste ou non n'a pas d'effet sur la méthode qui la crée.

    Citation Envoyé par Air P-E Voir le message
    Pour l'avoir utilisé récemment, en deboguant j'avais l'impression qu'avec le yield, ma méthode (contenant le yield) pouvait être appelée plusieurs fois.
    Par exemple une fois lors de l'appel pour récupérer une liste, une autre fois pour faire un simple count et ainsi de suite.
    Si tu as une méthode comme ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    public IEnumerable<int> GetNumbers()
    {
        foreach(int i in list)
        {
            yield return i;
        }
    }
    et que tu l'appelles plusieurs fois en faisant ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    int count = GetNumbers().Count();
    foreach(var num in GetNumbers())
    {
        ...
    }
    Elle va effectivement être exécutée plusieurs fois, mais c'est logique, puisque tu l'as appelée plusieurs fois. Ce qui est un peu moins évident à comprendre, c'est que même si tu fais ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    var numbers = GetNumbers();
    int count = numbers.Count();
    foreach(var num in numbers)
    {
        ...
    }
    ton bloc itérateur sera également exécuté 2 fois. En effet, ce qui déclenche son exécution n'est pas l'appel de la méthode, mais l'énumération du résultat. Ici le résultat est énuméré 2 fois, donc l'itérateur est exécuté 2 fois. Pour éviter ça, il faut "matérialiser" le résultat, en appelant par exemple ToList ou ToArray :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    var numbers = GetNumbers().ToList();
    int count = numbers.Count();
    foreach(var num in numbers)
    {
        ...
    }

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Mot Cle "N" dans ma requete SQL
    Par rgarnier dans le forum SQL
    Réponses: 12
    Dernier message: 21/07/2009, 11h09
  2. Desactiver mot-cle Date dans un Insert
    Par nuriel2 dans le forum C#
    Réponses: 2
    Dernier message: 19/10/2007, 15h58
  3. mot cle invisible dans l'index(workshop help html)
    Par bbelle08 dans le forum Balisage (X)HTML et validation W3C
    Réponses: 9
    Dernier message: 17/08/2007, 09h32
  4. Réponses: 2
    Dernier message: 22/02/2006, 11h18
  5. sécuriser le mot de passe dans une page asp
    Par Redouane dans le forum ASP
    Réponses: 2
    Dernier message: 10/03/2004, 21h16

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo