Utiliser Yield pour parcourir un lecteur de données pourrait ne pas fermer la connexion ?

StackOverflow https://stackoverflow.com/questions/47521

  •  09-06-2019
  •  | 
  •  

Question

Voici un exemple de code pour récupérer des données d'une base de données en utilisant le mot clé rendement que j'ai trouvé à quelques endroits en cherchant sur Google :

public IEnumerable<object> ExecuteSelect(string commandText)
{
    using (IDbConnection connection = CreateConnection())
    {
        using (IDbCommand cmd = CreateCommand(commandText, connection))
        {
             connection.Open();
             using (IDbDataReader reader = cmd.ExecuteReader())
             {
                while(reader.Read())
                {
                    yield return reader["SomeField"];
                }
             }
             connection.Close();
        }
    }
}

Ai-je raison de penser que dans cet exemple de code, la connexion ne serait pas fermée si nous ne parcourions pas l'ensemble du lecteur de données ?

Voici un exemple qui ne fermerait pas la connexion, si je comprends bien le rendement.

foreach(object obj in ExecuteSelect(commandText))
{
  break;
}

Pour une connexion à la base de données qui pourrait ne pas être catastrophique, je suppose que le GC finirait par la nettoyer, mais que se passerait-il si au lieu d'une connexion il s'agissait d'une ressource plus critique ?

Était-ce utile?

La solution

L'itérateur que le compilateur synthétise implémente IDisposable, qui appelle foreach lorsque la boucle foreach est quittée.

La méthode Dispose() de l'Iterator nettoiera les instructions using en cas de sortie anticipée.

Tant que vous utilisez l'itérateur dans une boucle foreach, en utilisant le bloc() ou que vous appelez la méthode Dispose() d'une autre manière, le nettoyage de l'itérateur aura lieu.

Autres conseils

La connexion sera fermée automatiquement puisque vous l'utilisez dans le bloc "using".

D'après le test simple que j'ai essayé, aku a raison, dispose est appelé dès la sortie du bloc foreach.

@David :Cependant, la pile d'appels est conservée entre les appels, donc la connexion ne serait pas fermée car lors du prochain appel, nous reviendrions à l'instruction suivante après le rendement, qui est le bloc while.

Je crois comprendre que lorsque l'itérateur est supprimé, la connexion sera également supprimée avec lui.Je pense également que Connection.Close ne serait pas nécessaire car il serait pris en charge lorsque l'objet serait supprimé en raison de la clause using.

Voici un programme simple que j'ai essayé de tester le comportement...

class Program
{
    static void Main(string[] args)
    {
        foreach (int v in getValues())
        {
            Console.WriteLine(v);
        }
        Console.ReadKey();

        foreach (int v in getValues())
        {
            Console.WriteLine(v);
            break;
        }
        Console.ReadKey();
    }

    public static IEnumerable<int> getValues()
    {
        using (TestDisposable t = new TestDisposable())
        {
            for(int i = 0; i<10; i++)
                yield return t.GetValue();
        }
    }
}

public class TestDisposable : IDisposable
{
    private int value;

    public void Dispose()
    {
        Console.WriteLine("Disposed");
    }

    public int GetValue()
    {
        value += 1;
        return value;
    }
}

A en juger par cette explication technique, votre code ne fonctionnera pas comme prévu, mais s'abandonnera sur le deuxième élément, car la connexion était déjà fermée lors du renvoi du premier élément.

@Joël Gauvreau :Oui, j'aurais dû continuer à lire. Partie 3 de cette série explique que le compilateur ajoute une gestion spéciale pour que les blocs final se déclenchent uniquement au niveau réel fin.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top