yield return 与 return IEnumerable<T>

yield return vs. return IEnumerable<T>

我注意到从 using 语句中的 IDataReader 中读取一些我无法理解的奇怪内容。虽然我确定答案很简单。

为什么在 using (SqlDataReader rd) { ... } 中,如果我直接执行 yield return,reader 在读取期间保持打开状态。但是,如果我执行直接 return 调用 SqlDataReader 扩展方法(如下所述),那么 reader 在实现可枚举之前关闭?

public static IEnumerable<T> Enumerate<T>(this SqlDataReader rd)
{
    while (rd.Read())
        yield return rd.ConvertTo<T>(); //extension method wrapping FastMember

    rd.NextResult();
}

为了完全清楚我在问什么,我不确定为什么以下内容根本不同:

A fleshed out example, as per @TimSchmelter's request:

/*
 * 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
}

Summary: Both versions of the method defer, but because ReadSomeProcExt doesn't defer execution, the reader is disposed before execution is passed back to the caller (i.e. before Enumerate<T> can run). ReadSomeProc, on the other hand, doesn't create the reader until it's been passed back to the caller, so it doesn't dispose the container until all its values have been read.

当你的方法使用yield return时,编译器实际上将编译后的代码更改为return和IEnumerable<>你的方法中的代码不会运行 直到其他代码开始迭代 returned IEnumerable<>.

这意味着下面的代码在 reader 和 return 值之前甚至没有 运行 您的 Enumerate 方法的第一行。当其他人开始迭代您的 returned IEnumerable<> 时,reader 已经被处理掉了。

using(SqlDataReader rd = cmd.ExecuteReader()){
    return rd.Enumerate<T>();
}

但是此代码将执行整个 Enumerate() 方法,以便在 returning:

之前生成 List<> 个结果
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
}

但是当他们执行 returned IEnumerable<> 时,using 块打开了,直到 IEnumerable<>Dispose()完成它的迭代,此时您已经从数据 reader.

中读取了您需要的所有内容

因为"yield return"会return一个元素继续迭代,而"normal"return会完成调用