Mini quizz Linq


Nan nan, je ne repars pas dans une série de quizzs :-).

En voici juste un petit vite fait !

private IEnumerable<string> GetValues()
{
    Console.WriteLine("Appel de GetValues");
    yield return "mitsu";
    yield return "pierre";
    yield return "dick";
}

var q = GetValues();

Console.WriteLine("Affichage des données:");

foreach (var s in q)
    Console.WriteLine(s);

Dans cet exemple, "Appel de GetValues" ne s’affichera pas lors de l’appel de GetValues() mais bien plus tard lors du foreach. Comment corriger celà ?

Mitsu

Comments (12)

  1. C’est peut être de la triche, mais bon :

    delegate IEnumerable<String> IteratorBuilder();

    private IEnumerable<string> GetValues()

    {

     Console.WriteLine("Appel de GetValues");

     IteratorBuilder builder = delegate() {

       yield return "mitsu";

       yield return "pierre";

       yield return "dick";  

     }

     return builder();

    }

  2. Nicolas, les méthodes anonymes (et les lambdas) ne peuvent pas être des blocs itérateurs…

    Comme dans cet exemple on itère sur toutes les valeurs, j’aurais tendance à remplacer :

    var q = GetValues();

    Par :

    var q = GetValues().ToArray();

  3. JeremyJeanson says:

    En utilisant une bonne vielle table 🙂

    var q = GetValues().ToArray();

  4. nlehuen says:

    Ca m’apprendra à faire comme si je connaissais C# 🙂

    Le problème de transformer votre itérateur en tableau, c’est que ça ne fait pas du tout la même chose en mémoire. Dans ce code là évidemment oui, mais supposons que l’itérateur parcourt 10 millions de ligne en BDD ? On charge toute la base en mémoire ?

    Bon ben si on ne peut pas le faire avec une méthode anonyme, il n’y a plus qu’à la nommer :

    private IEnumerable<string> GetValuesIterator() {

      yield return "mitsu";

      yield return "pierre";

      yield return "dick";  

    }

    private IEnumerable<string> GetValues()

    {

      Console.WriteLine("Appel de GetValues");

      return GetValuesIterator();

    }

    Mais Mitsu va peut-être nous sortir une astuce de derrière les fagots ?

  5. Yaume says:

    Hello,

    Il me semble qu’il y a une méthode d’extension transformant un IQueryable en IEnumerable:

    var q = GetValues();

    devient

    var q = GetValues().AsEnumerable();

  6. Yaume says:

    Et bien non, cela ne fonctionne pas.

    En fait le ToArray() semble pas mal mais l’on peut réutiliser le ToEnumerable() derrière pour garder une énumération.

    var q=GetValues().ToArray().AsEnumerable();

    A noter que le .ToArray semble donner un temps d’éxecution variable (entre 7 et 30 ticks sur mon poste) alors que le .ToList semble constant (9 ticks sur mon poste).

    Donc plutot :

    var q=GetValues().ToList().AsEnumerable();

    PS : Le AsEnumerable ne semble en fait qu’une indication pour le compilateur. Un cast explicit ferait la même chose … sauf qu’ici pas besoin de spécifier le type.

  7. Je suis complètement d’accord avec toi Nicolas, un ToArray() provoque l’exécution complète du bloc itérateur. D’où ma précision : "Comme dans cet exemple on itère sur toutes les valeurs" ça ne change rien…

    Si on veut conserver l’exécution différée alors la solution la plus juste consiste effectivement à utiliser deux méthodes. La première contient les WriteLine (ou plus probablement des guard clauses) et la seconde encapsule la logique d’itération.

    Et je ne crois pas qu’il y ait d’autres astuces derrière les fagots 🙂

  8. Le ToArray, je ne suis pas pour.

    Perso dans ce cas, je reviendrais aux fondamentaux : comment marche le foreach ?

    Pour cela je propose le code suivant :

    var q = GetValues();

    var enumerator = q.GetEnumerator();

    bool hasNextValue = enumerator.MoveNext();

    Console.WriteLine("Affichage des données:");

    while (hasNextValue)

    {

       Console.WriteLine(enumerator.Current);

       hasNextValue = enumerator.MoveNext();

    }

    C’est sûr, il n’y a pas de C#3 dans ce code mais il n’est pas toujours utile de faire du C#3. Des fois un code en C#2 marche très bien. (J’ai du mal à croire que c’est moi qui dit ça :))

  9. Mitsu Furuta says:

    Héhé, excellente réponse de maître Nicolas himself !

    C’est une très bonne surprise et un honneur que de te retrouver ici d’ailleurs :p.

    private IEnumerable<string> GetValuesIterator() {

     yield return "mitsu";

     yield return "pierre";

     yield return "dick";  

    }

    private IEnumerable<string> GetValues()

    {

     Console.WriteLine("Appel de GetValues");

     return GetValuesIterator();

    }

    En effet le compilateur C# dénature complètement la compilation d’une méthode retournant un IEnumerable si celle-ci contient des instructions "yield return".

    Il encapsule la création d’un objet iterateur en y embarquant le code qui ne sera exécuté qu’à l’utilisation de cet itérateur.

    Mais si elle n’en contient pas…c’est une méthode comme une autre. Son exécution n’est donc pas différée !

    Well done !

  10. nlehuen says:

    Merci !

    En fait je lis ton blog depuis pas mal de temps, ce qui m’a fait participer en dépit de ma non connaissance de C#, c’est la proximité du sujet (itérateurs) avec Python, un langage que j’ai beaucoup utilisé ces dernières années. Le mot clé yield a été introduit dans Python 2.2 fin 2001 et le mécanisme transformant une fonction en générateur avec le side effect que tu montres dans cet article fait partie des choses auxquelles on a dû se frotter 🙂

    @+

    Nicolas

  11. Jean-François Beaulieu says:

    Why not simply write :

    Console.WriteLine(GetValues().Aggregate(string.Empty, (current, next) => current + Environment.NewLine + next));