2009-09-10 8 views
19

Czy istnieje odpowiedni sposób na wyjście z foreach tak, że IEnumerable <> wie, że jestem skończony i powinien posprzątać.Obawa o zwrot plonów i zerwanie z foreach

Rozważmy następujący kod:

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

jeśli on konsument nie zrywa z foreach wtedy wszystko jest w porządku, a czytelnik zwróci false, pętla willend natomiast a funkcja czyści komendy bazy danych i połączenia . Ale co się stanie, jeśli dzwoniący zerwie z foreach, zanim skończę?

+1

zobaczyć również http://stackoverflow.com/questions/1400146/are-there-any-pitfalls-to-using-an-ienumerablet-return-type-for-sql-data/1400195# 1400195 –

Odpowiedz

30

Doskonałe pytanie. Nie musisz się tym martwić; kompilator zajmie się tym za Ciebie. Zasadniczo, to co robimy, umieszczamy kod oczyszczający dla końcowych bloków w specjalnej metodzie oczyszczania na generowanym iteratorze. Kiedy formant opuszcza blok obiegowy wywołującego, kompilator generuje kod, który wywołuje kod czyszczenia w iteratorze.

Uproszczony przykład:

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

Twoje pytanie jest w zasadzie "Czy Cleanup() nazywa się w tym scenariuszu?"

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

Tak. Blok iterator jest generowany jako klasa z metody Dispose że nazywa Oczyszczanie, a następnie pętla foreach jest generowany jako coś podobnego do:

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

Więc gdy przerwa nastąpi, wreszcie przejmuje i dysponentem jest nazywany .

Zobacz moją ostatnią serię artykułów, jeśli chcesz uzyskać więcej informacji o niektórych dziwnych przypadkach narożnych, które rozważaliśmy przy projektowaniu tej funkcji.

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

+0

To jest świetna odpowiedź! –

2

Można użyć instrukcji

yield break; 

wyrwać się z pętli plastyczności wcześnie, ale kod ujawnia nieporozumień Myślę ... Podczas korzystania z „używając” oświadczenie,

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

możesz automatycznie uzyskać ostateczny blok w skompilowanym kodzie IL, a blok finalny wywoła funkcję "Zrzuć", a w kodzie utylizacji połączenie zostanie zamknięte ...

+0

Drobna korekta: 'break yield' jest stwierdzeniem. – jason

+0

Edytowane w celu poprawienia –

2

Zobaczmy, czy dostanę twoje pytanie.

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

ze względu na słowo kluczowe foreach, moduł wyliczający jest prawidłowo usuwany. Podczas usuwania Enumerator, wykonanie getPeople() zostaje zakończone. Więc połączenie jest odpowiednio oczyszczone.