25 Stimmen

die Sorge um die Rendite und den Abbruch einer foreach

Gibt es einen richtigen Weg, um von einem foreach zu brechen, so dass die IEnumerable<> weiß, dass ich fertig bin und es aufräumen sollte.

Betrachten Sie den folgenden Code:

    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 der Verbraucher die foreach-Schleife nicht verlässt, ist alles in Ordnung und der Leser gibt false zurück, die while-Schleife wird beendet und die Funktion bereinigt den Datenbankbefehl und die Verbindung. Aber was passiert, wenn der Aufrufer die foreach-Schleife abbricht, bevor ich fertig bin?

37voto

Eric Lippert Punkte 628543

Ausgezeichnete Frage. Sie brauchen sich darüber keine Gedanken zu machen; der Compiler erledigt das für Sie. Im Grunde genommen haben wir den Aufräumcode für die finally-Blöcke in eine spezielle Aufräummethode für den generierten Iterator eingefügt. Wenn die Kontrolle den foreach-Block des Aufrufers verlässt, generiert der Compiler einen 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 lautet im Grunde: "Wird Cleanup() in diesem Szenario aufgerufen?"

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

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

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

Wenn also die Unterbrechung eintritt, übernimmt der "Finally" und der "Disposer" wird aufgerufen.

Wenn Sie mehr über einige der seltsamen Eckfälle erfahren möchten, die wir bei der Entwicklung dieser Funktion berücksichtigt haben, lesen Sie meine aktuelle Artikelserie.

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

2voto

Charles Bretana Punkte 137391

Sie können die Anweisung

yield break;

um aus einer Yield-Schleife frühzeitig auszubrechen, aber Ihr Code offenbart ein Missverständnis, denke ich... Wenn Sie die "using"-Anweisung verwenden,

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

erhalten Sie AUTOMATISCH einen Try-Finish-Block in der kompilierten IL-Code, und der finnaly-Block wird Dispose aufrufen, und in der Dispose-Code wird die Verbindung geschlossen werden...

2voto

Canton Punkte 797

Mal sehen, ob ich Ihre Frage verstehe.

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

aufgrund des foreach-Schlüsselworts wird der Enumerator ordnungsgemäß entsorgt. Während der Beseitigung von Enumerator wird die Ausführung von getPeople() beendet. Die Verbindung wird also ordnungsgemäß aufgeräumt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X