Usingステートメント内のIDataReader
から読み取ることに、理解できない何か不思議なことに気づきました。答えは簡単だと思いますが。
using (SqlDataReader rd) { ... }
内で、yield return
を直接実行すると、読み取りの間、リーダーが開いたままになるのはなぜですか。しかし、列挙可能なものが実現される前にリーダーが閉じるSqlDataReader拡張メソッド(以下に概説)を直接return
呼び出して実行するとどうなりますか?
public static IEnumerable<T> Enumerate<T>(this SqlDataReader rd)
{
while (rd.Read())
yield return rd.ConvertTo<T>(); //extension method wrapping FastMember
rd.NextResult();
}
私が何を求めているのかを完全に明確にするために、次のことが根本的に異なる理由がわかりません。
@TimSchmelterのリクエストによる、具体化された例:
/*
* contrived methods
*/
public IEnumerable<T> ReadSomeProc<T>() {
using (var db = new SqlConnection("connection string"))
{
var cmd = new SqlCommand("dbo.someProc", db);
using(var rd = cmd.ExecuteReader())
{
while(rd.Read())
yield return rd.ConvertTo<T>(); //extension method wrapping FastMember
}
}
}
//vs
public IEnumerable<T> ReadSomeProcExt<T>() {
using (var db = new SqlConnection("connection string"))
{
var cmd = new SqlCommand("dbo.someProc", db);
using(var rd = cmd.ExecuteReader())
{
return rd.Enumerate<T>(); //outlined above
}
}
}
/*
* usage
*/
var lst = ReadSomeProc<SomeObect>();
foreach(var l in lst){
//this works
}
//vs
var lst2 = ReadSomeProcExt<SomeObect>();
foreach(var l in list){
//throws exception, invalid attempt to read when reader is closed
}
概要:両方のバージョンのメソッドdeferですが、
ReadSomeProcExt
は実行を延期しないため、リーダー実行が呼び出し元に返される前に(つまり、Enumerate<T>
が実行される前に)破棄されます。一方、ReadSomeProc
は、呼び出し元に返されるまでリーダーを作成しないため、すべての値が読み取られるまでコンテナーを破棄しません。
メソッドがyield return
を使用する場合、コンパイラーは実際にコンパイルされたコードを変更してIEnumerable<>
を返します。メソッド内のコードは、返されたIEnumerable<>
に対して他のコードが反復を開始するまで実行されません。
つまり、以下のコードは、リーダーを破棄して値を返す前に、Enumerate
メソッドの最初の行を実行しません。他の誰かがあなたの返されたIEnumerable<>
を繰り返し始めたときまでに、リーダーはすでに処分されています。
using(SqlDataReader rd = cmd.ExecuteReader()){
return rd.Enumerate<T>();
}
ただし、このコードは、次を返す前に結果のList<>
を生成するために、Enumerate()
メソッド全体を実行します。
using(SqlDataReader rd = cmd.ExecuteReader()){
return rd.Enumerate<T>().ToList();
}
一方、このコードを使用して呼び出しした人は、結果が評価されるまで実際にはメソッドを実行しません。
using(SqlDataReader rd = cmd.ExecuteReader()){
while(rd.Read())
yield return rd.ConvertTo<T>(); //extension method wrapping FastMember
}
しかし、返されたIEnumerable<>
を実行すると、using
ブロックが開き、IEnumerable<>
が反復を終了するまで、Dispose()
は実行されません。その時点で、必要なものはすべて既に読み取られています。データリーダーから。
これは、「yield return」が1つの要素を返し、反復を続行するのに対し、「normal」returnは呼び出しを終了するためです。