Quizz : DataReader + yield + foreach + Dispose

Allez, un petit dernier pour la route… Dans le code ci-dessous, j’utilise le mot-clé yield pour boucler sur toutes les lignes d’un DataReader. Le mot-clé using permet de disposer le DataReader, mais seulement lorsqu’on atteint la fin du parcours du IEnumerable.

Question : que se passe-t-il si, à l’aide d’un index, on empêche le code de l’itérateur d’aller à la fin. Comment peut-on s’assurer que le Reader sera tout de même clos ? Et surtout, comment cela fonctionne-t-il dans le cas d’un foreach, sur lequel nous n’avons pas un accès à l’itérateur ?

namespace ConsoleApplication1
{
    public class Dossier
    {
        public string Code { get; set; }
        public string Libelle { get; set; }
 
        public static Dossier Create(IDataRecord entree)
        {
            return new Dossier()
            {
                Code = entree["idcompos"].ToString(),
                Libelle = entree["lbcompos"].ToString()
            };
        }
    }
 
    class Program
    {
        public static SqlConnection Connexion = null;
 
        public static IEnumerable<Dossier> GetDossiers()
        {
            using (var dr = GetReader())
                while (dr.Read())
                    yield return Dossier.Create(dr);
        }
 
        public static IDataReader GetReader()
        {
            Connexion = new SqlConnection(_ChaineConnexion);
            Connexion.Open();
            SqlCommand Commande = new SqlCommand("SELECT a, b FROM TEST", Connexion);
            return Commande.ExecuteReader(CommandBehavior.CloseConnection);
        }

        static void Main(string[] args)
        {
            int NombreLecturesMaxi = 5;
            foreach (Dossier dossier in GetDossiers())
            {
                Console.WriteLine(dossier.Libelle);
                if (NombreLecturesMaxi-- < 0) break;
            }
            Console.WriteLine("Etat de la connexion : {0}", Connexion.State);
 
            NombreLecturesMaxi = 5;
            IEnumerator<Dossier> Enumerateur = GetDossiers().GetEnumerator();
            while (Enumerateur.MoveNext())
            {
                Console.WriteLine(Enumerateur.Current.Libelle);
                if (NombreLecturesMaxi-- < 0) break;
            }
            Console.WriteLine("Etat de la connexion : {0}", Connexion.State);
            Enumerateur.Dispose();
            Console.WriteLine("Etat de la connexion : {0}", Connexion.State);
        }
    }
}

Réponse : si vous exécutez ce code, vous verrez que le Dispose sur l’énumérateur est bien propagé, et que cela clos le DataReader, et donc la connexion puisqu’on a utilisé le CommandBehaviour.CloseConnection. Du coup, l’état de la connexion est ouvert avant le Dispose et fermé après.

Mais dans le cas du foreach, il est directement fermé : le fait de sortir de la portée du foreach appelle le Dispose sur l’énumérateur.

Si on jette un oeil à l’IL, c’est bien ce qui est créé par le compilateur :

image

Je m’émerveille peut-être de quelque chose de tout à fait normal, mais avant de me poser la question de la clôture du DataReader sur un foreach simulé de manière incomplète, je ne suis pas sûr que j’aurai pensé à bien appeler le Dispose pour le propager…

About JP Gouigoux

Jean-Philippe Gouigoux est Architecte Logiciel, MVP Connected Systems Developer. Il intervient régulièrement à l'Université de Bretagne Sud ainsi qu'à l'Agile Tour. Plus de détails sur la page "Curriculum Vitae" de ce blog.
This entry was posted in .NET. Bookmark the permalink.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha Captcha Reload