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 :
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…