2009-09-10 7 views
19

¿Hay una forma adecuada de romper con un foreach tal que el IEnumerable <> sepa que he terminado y debería limpiarlo?una preocupación acerca del retorno de rendimiento y la ruptura de un foreach

Considere el siguiente código:

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

Si el consumidor no se rompe desde el foreach continuación Everthing está muy bien y el lector volverá falso, el willend bucle while y la función limpia el comando de base de datos y la conexión . Pero, ¿qué sucede si la persona que llama se separa del foreach antes de que yo termine?

+1

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

Respuesta

30

Excelente pregunta. No necesitas preocuparte por esto; el compilador se encarga de eso por ti. Básicamente, lo que hacemos es poner el código de limpieza para los bloques finally en un método de limpieza especial en el iterador generado. Cuando el control abandona el bloque foreach de la persona que llama, el compilador genera un código que llama al código de limpieza en el iterador.

Un ejemplo simplificado:

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

Su pregunta es, básicamente, "es la limpieza() se llama en este escenario?"

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

Sí. El bloque de iterador se genera como una clase con un método Dispose que llama Liberador, y luego el bucle foreach se genera como algo similar a:

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

Por eso, cuando la ruptura ocurre, el finalmente asume el control y el triturador se llama .

Consulte mi serie reciente de artículos si desea obtener más información sobre algunos de los casos de esquinas raros que consideramos en el diseño de esta función.

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

+0

¡Esta es una gran respuesta! –

2

Puede utilizar la instrucción

yield break; 

para salir de un bucle de producción precoz, pero su código revela un malentendido ... Creo Cuando se utiliza el "uso" declaración,

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

AUTOMÁTICAMENTE obtienes un intento de bloquear en el código IL compilado, y el bloque final llamará a Dispose, y en el código Dispose se cerrará la conexión ...

+0

Corrección secundaria: 'yield break' es una afirmación. – jason

+0

Editado para corregir –

2

Veamos si recibo tu pregunta.

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

debido a la palabra clave foreach, el enumerador está correctamente eliminado. Durante la eliminación de Enumerator, finaliza la ejecución de getPeople(). Por lo tanto, la conexión se limpia correctamente.

Cuestiones relacionadas