2009-09-10 6 views
19

Existe-t-il un moyen approprié de rompre avec un foreach de telle sorte que le IEnumerable <> sache que j'ai terminé et qu'il devrait nettoyer.une inquiétude sur le retour de rendement et la rupture d'un foreach

Consultez le code suivant:

private static IEnumerable<Person> getPeople() 
    { 
     using (SqlConnection sqlConnection = new SqlConnection("...")) 
     { 
      try 
      { 
       sqlConnection.Open(); 
       using (SqlCommand sqlCommand = new SqlCommand("select id, firstName, lastName from people", sqlConnection)) 
       { 

        using (SqlDataReader reader = sqlCommand.ExecuteReader()) 
        { 
         while (reader.Read()) 
          yield return new Person(reader.GetGuid(0), reader.GetString(1), reader.GetString(2)); 
        } 
       } 
      } 
      finally 
      { 
       Console.WriteLine("finally disposing of the connection"); 
       if (sqlConnection.State == System.Data.ConnectionState.Open) 
        sqlConnection.Close(); 
      } 
     } 
    } 

S'il consommateur ne rompt pas du foreach alors everthing est très bien et le lecteur retournera false, la boucle en willend et la fonction nettoie la commande de base de données et connexion . Mais que se passe-t-il si l'appelant se sépare de la foreach avant que j'aie fini?

+1

voir aussi http://stackoverflow.com/questions/1400146/are-there-any-pitfalls-to-using-an-ienumerablet-return-type-for-sql-data/1400195# 1400195 –

Répondre

30

Excellente question. Vous n'avez pas besoin de vous inquiéter à ce sujet; le compilateur en prend soin pour vous. Fondamentalement, ce que nous faisons est de mettre le code de nettoyage pour les blocs finally dans une méthode de nettoyage spéciale sur l'itérateur généré. Lorsque le contrôle quitte le bloc foreach de l'appelant, le compilateur génère du code qui appelle le code de nettoyage sur l'itérateur.

Un exemple simplifié:

static IEnumerable<int> GetInts() 
{ 
    try { yield return 1; yield return 2;} 
    finally { Cleanup(); } 
} 

Votre question est essentiellement "Est-nettoyage() appelé dans ce scénario?"

foreach(int i in GetInts()) { break; } 

Oui. Le bloc itérateur est généré en tant que classe avec une méthode Dispose qui appelle le nettoyage, puis la boucle foreach est générée comme quelque chose de similaire à:

{ 
    IEnumerator<int> enumtor = GetInts().GetEnumerator(); 
    try 
    { 
    while(enumtor.MoveNext()) 
    { 
     i = enumtor.Current; 
     break; 
    } 
    } 
    finally 
    { 
    enumtor.Dispose(); 
    } 
} 

Alors, quand la rupture se produit, la prend enfin terminée et l'éliminateur est appelé .

Voir ma série d'articles récents si vous voulez plus d'informations sur certains des cas d'angle étranges que nous avons pris en compte dans la conception de cette fonctionnalité.

http://blogs.msdn.com/ericlippert/archive/tags/Iterators/default.aspx

+0

Ceci est une excellente réponse! –

2

Vous pouvez utiliser l'instruction

yield break; 

à sortir d'une boucle de rendement au début, mais votre code révèle un malentendu, je pense ... Lorsque vous utilisez l'instruction « à l'aide »,

using (SqlConnection sqlConnection = new SqlConnection("...")) 
{ 
    // other stuff 
} 

vous encaissez AUTOMATIQUEMENT essayer enfin bloquer dans le code IL compilé, et le bloc finnaly appellerez à Evacuer et dans le code Éliminez la connexion sera fermé ...

+0

Correction mineure: 'yield break' est une déclaration. – jason

+0

Modifié pour corriger –

2

Voyons voir si j'ai votre question.

foreach(Person p in getPeople()) 
{ 
    // break here 
} 

à cause du mot-clé foreach, le recenseur est bien disposé. Pendant la mise à disposition d'Enumerator, l'exécution de getPeople() est terminée. Donc, la connexion est correctement nettoyée.

Questions connexes