web-dev-qa-db-ja.com

イールドリターンとリターンIEnumerable <T>

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
}
14
pimbrouwers

概要:両方のバージョンのメソッド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()は実行されません。その時点で、必要なものはすべて既に読み取られています。データリーダーから。

11

これは、「yield return」が1つの要素を返し、反復を続行するのに対し、「normal」returnは呼び出しを終了するためです。

0
Wilmer