Mientras que la comprensión de cómo yield
funciona la palabra clave, me encontré con Link1 y Link2 en StackOverflow que aboga por el uso de yield return
tiempo interactuando sobre el DataReader y se adapta a mi necesidad también. Pero me pregunto qué sucede si uso lo yield return
que se muestra a continuación y si no repito todo el DataReader, ¿la conexión de la base de datos permanecerá abierta para siempre?
IEnumerable<IDataRecord> GetRecords()
{
SqlConnection myConnection = new SqlConnection(@"...");
SqlCommand myCommand = new SqlCommand(@"...", myConnection);
myCommand.CommandType = System.Data.CommandType.Text;
myConnection.Open();
myReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
try
{
while (myReader.Read())
{
yield return myReader;
}
}
finally
{
myReader.Close();
}
}
void AnotherMethod()
{
foreach(var rec in GetRecords())
{
i++;
System.Console.WriteLine(rec.GetString(1));
if (i == 5)
break;
}
}
Intenté el mismo ejemplo en una aplicación de consola de ejemplo y noté durante la depuración que el bloque finalmente GetRecords()
no se ejecuta. ¿Cómo puedo asegurar el cierre de DB Connection? ¿Hay una mejor manera que usar yield
palabras clave? Estoy tratando de diseñar una clase personalizada que se encargará de ejecutar SQL seleccionados y procedimientos almacenados en la base de datos y devolverá el resultado. Pero no quiero devolver el DataReader a la persona que llama. También quiero asegurarme de que la conexión se cerrará en todos los escenarios.
Editar Cambió la respuesta a la respuesta de Ben, ya que es incorrecto esperar que quienes llaman al método usen el método correctamente y con respecto a la conexión de la base de datos, será más costoso si el método se llama varias veces sin razón alguna.
Gracias Jakob y Ben por la explicación detallada.
Respuestas:
Sí, enfrentará el problema que describe: hasta que termine de iterar sobre el resultado, mantendrá la conexión abierta. Hay dos enfoques generales en los que puedo pensar para lidiar con esto:
Empujar, no tirar
Actualmente está devolviendo una
IEnumerable<IDataRecord>
estructura de datos que puede extraer. En cambio, podría cambiar su método para sacar sus resultados. La forma más sencilla sería pasar unAction<IDataRecord>
que se llama en cada iteración:Tenga en cuenta que dado que se trata de una colección de elementos,
IObservable
/IObserver
podría ser una estructura de datos ligeramente más apropiada, pero a menos que la necesite, una simpleAction
es mucho más sencilla.Evaluar con entusiasmo
Una alternativa es simplemente asegurarse de que la iteración se complete por completo antes de regresar.
Normalmente puede hacer esto simplemente colocando los resultados en una lista y luego devolviéndolo, pero en este caso existe la complicación adicional de que cada elemento sea la misma referencia para el lector. Entonces necesita algo para extraer el resultado que necesita del lector:
fuente
Su
finally
bloqueo siempre se ejecutará.Cuando use
yield return
el compilador creará una nueva clase anidada para implementar una máquina de estados.Esta clase contendrá todo el código de los
finally
bloques como métodos separados. Realizará un seguimiento de losfinally
bloques que deben ejecutarse según el estado. Todos losfinally
bloques necesarios se ejecutarán en elDispose
método.De acuerdo con la especificación del lenguaje C #,
foreach (V v in x) embedded-statement
se expande aentonces el enumerador se eliminará incluso si sale del bucle con
break
oreturn
.Para obtener más información sobre la implementación del iterador, puede leer este artículo de Jon Skeet
Editar
El problema con este enfoque es que confía en los clientes de su clase para usar el método correctamente. Podrían, por ejemplo, obtener el enumerador directamente e iterarlo en un
while
bucle sin desecharlo.Debería considerar una de las soluciones sugeridas por @BenAaronson.
fuente
var records = GetRecords(); var first = records.First(); var count = records.Count()
realidad ejecutará ese método dos veces, abriendo y cerrando la conexión y el lector cada vez. Iterarlo dos veces hace lo mismo. También puede pasar el resultado durante mucho tiempo antes de iterar sobre él, etc. Nada en la firma del método implica ese tipo de comportamientoGetRecords
returnIEnumerable
, espero que cargue los registros en la memoria. Por otro lado, si fuera una propiedadRecords
, preferiría asumir que es algún tipo de abstracción sobre la base de datos.Independientemente del
yield
comportamiento específico , su código contiene rutas de ejecución que no eliminarán sus recursos correctamente. ¿Qué pasa si su segunda línea arroja una excepción o su tercera? ¿O incluso tu cuarto? Necesitará una cadena de prueba / finalmente muy compleja, o puede usarusing
bloques.La gente ha comentado que esto no es intuitivo, que las personas que llaman a su método pueden no saber que enumerar el resultado dos veces lo llamará dos veces. Bueno, mala suerte. Así funciona el lenguaje. Esa es la señal que
IEnumerable<T>
envía. Hay una razón por la cual esto no regresaList<T>
oT[]
. Las personas que no saben esto necesitan ser educadas, no evitadas.Visual Studio tiene una característica llamada análisis de código estático. Puede usarlo para averiguar si ha eliminado los recursos correctamente.
fuente
IEnumerable
para hacer todo tipo de cosas, como lanzar excepciones totalmente no relacionadas o escribir archivos de 5GB en el disco. El hecho de que el lenguaje lo permita no significa que sea aceptable devolver unIEnumerable
método que lo hace desde un método que no indica que eso sucederá.IEnumerable<T>
es la indicación de que enumerarlo dos veces resultará en dos llamadas. Incluso recibirá advertencias útiles en sus herramientas si enumera un número enumerable varias veces. El punto sobre lanzar excepciones es igualmente válido independientemente del tipo de retorno. Si ese método devolviera unList<T>
, arrojaría las mismas excepciones.IEnumerable
advertencia. Pero también creo que, como escritor de métodos, tiene la responsabilidad de cumplir con lo que implica su firma. Se llama al métodoGetRecords
, por lo que cuando regrese, esperaría que obtuviera los registros . Si se llamaraGiveMeSomethingICanPullRecordsFrom
(o, de manera más realistaGetRecordSource
o similar), un perezosoIEnumerable
sería más aceptable.File
,ReadLines
yReadAllLines
se puede distinguir fácilmente y eso es algo bueno. (Aunque es más debido al hecho de que una sobrecarga de método no puede diferir solo por tipo de retorno). Quizás ReadRecords y ReadAllRecords también encajarían aquí como esquema de nomenclatura.Lo que hiciste parece estar mal, pero funcionará bien.
Debe usar un IEnumerator <> , ya que también hereda IDisposable .
Pero debido a que está utilizando un foreach, el compilador sigue utilizando un IDisposable para generar un IEnumerator <> para el foreach.
de hecho, el foreach involucra muchas cosas internamente.
Consulte /programming/13459447/do-i-need-to-consider-disposing-of-any-ienumerablet-i-use
fuente