2009-09-10 2 views
19

Gibt es eine ordnungsgemäße Möglichkeit, von einem Foreach so zu brechen, dass der IEnumerable <> weiß, dass ich fertig bin und es aufräumen sollte.ein Problem über die Rendite zurück und brechen von einem Foreach

Betrachten Sie den folgenden Code ein:

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(); 
      } 
     } 
    } 

Wenn er Verbraucher von der foreach nicht bricht dann das Everthing in Ordnung ist und der Leser wird false zurück, die while-Schleife willend und die Funktion der Datenbankbefehl und Verbindung bereinigt . Aber was passiert, wenn der Anrufer aus der Foreach bricht, bevor ich fertig bin?

+1

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

Antwort

30

Ausgezeichnete Frage. Du brauchst dir darüber keine Sorgen zu machen. der Compiler erledigt das für Sie. Im Grunde nehmen wir den Bereinigungscode für die finally-Blöcke in eine spezielle Bereinigungsmethode für den generierten Iterator. Wenn die Steuerung den foreach-Block des Aufrufers verlässt, generiert der Compiler Code, der den Bereinigungscode für den Iterator aufruft.

Ein vereinfachtes Beispiel:

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

Ihre Frage "Ist Cleanup() in diesem Szenario genannt?" Ist im Grunde

Ja. Der Iteratorblock wird als eine Klasse mit einer Dispose-Methode erzeugt, die Cleanup aufruft, und dann wird die foreach-Schleife erzeugt als etwas Ähnliches wie:

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

Also, wenn der Bruch geschieht, nimmt die schließlich über und die Entsorger aufgerufen .

Siehe meine aktuelle Artikelserie, wenn Sie mehr über einige der seltsamen Eckfälle wissen möchten, die wir beim Design dieser Funktion berücksichtigt haben.

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

+0

Dies ist eine gute Antwort! –

2

können Sie mit der Anweisung

yield break; 

früh aus einer Ausbeute Schleife zu brechen, aber Ihr Code zeigt ein Missverständnis Ich denke, ... Wenn Sie die „using“ Anweisung,

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

Sie automatisch versuchen, schließlich blockieren in der kompilierten IL-Code, und der Finnaly Block wird Dispose aufrufen, und in der Dispose-Code wird die Verbindung geschlossen ...

+0

Geringfügige Korrektur: 'yield break' ist eine Aussage. – jason

+0

Bearbeitet um zu korrigieren –

2

Mal sehen, ob ich deine Frage bekomme.

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

Aufgrund des foreach-Schlüsselworts wird der Enumerator ordnungsgemäß entsorgt. Während der Entsorgung von Enumerator wird die Ausführung von getPeople() beendet. So wird die Verbindung richtig aufgeräumt.