收益回报与收益IEnumerable T [英] yield return vs. return IEnumerable<T>

查看:100
本文介绍了收益回报与收益IEnumerable T的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我注意到我对无法理解的using语句中的 IDataReader 进行读取感到有些好奇。虽然我确信答案很简单。



为什么在内使用(SqlDataReader rd){...} 如果我直接执行收益率,则阅读器在读取期间保持打开状态。但是,如果我执行直接的 return 调用SqlDataReader扩展方法(如下所示),该方法在实现可枚举之前关闭了读者?

 公共静态IEnumerable< T>枚举< T>(此SqlDataReader rd)
{
而(rd.Read())
的收益率返回rd.ConvertTo< T>(); //扩展方法包装FastMember

rd.NextResult();
}

要绝对清楚我要问的是什么,我不确定为什么以下是根本不同的内容:


根据@TimSchmelter的要求,一个充实的示例:




  / * 
*人为设计的方法
* /
public IEnumerable< T> ReadSomeProc< T>(){
使用(var db = new SqlConnection(连接字符串))
{
var cmd = new SqlCommand( dbo.someProc,db);

using(var rd = cmd.ExecuteReader())
{
while(rd.Read())
yield return rd.ConvertTo< T>() ; //扩展方法包装FastMember
}
}
}


// vs
public IEnumerable< T> ReadSomeProcExt< T>(){
使用(var db = new SqlConnection(连接字符串))
{
var cmd = new SqlCommand( dbo.someProc,db);

using(var rd = cmd.ExecuteReader())
{
return rd.Enumerate< T>(); //在
之上概述
}
}

/ *
*使用情况
* /
var lst = ReadSomeProc< SomeObect>();

foreach(var in lst){
//此函数
}

// vs
var lst2 = ReadSomeProcExt< SomeObect> ;();

foreach(列表中的var l){
//抛出异常,关闭阅读器后尝试读取无效
}


解决方案


摘要:方法的两个版本 defer ,但是由于 ReadSomeProcExt 不会延迟执行,因此在执行传递回调用方之前(即在之前)会处理读取器。枚举< T> 可以运行)。另一方面, ReadSomeProc 不会创建读取器,直到将其传递回调用方为止,因此直到所有值都被读取后,它才处理容器


当您的方法使用 yield return 时,编译器实际上会更改已编译的返回 IEnumerable<> 的代码,并且在其他代码开始对返回的 IEnumerable<进行迭代之前,方法中的代码将不会运行。 >



这意味着下面的代码甚至没有运行 Enumerate 方法,然后将其分配给读取器并返回一个值。当其他人开始遍历您返回的 IEnumerable<> 时,阅读器已被处置。

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

但是此代码将执行整个 Enumerate() 方法,以便在返回之前生成结果 List<>

  using(SqlDataReader rd = cmd.ExecuteReader()){
返回rd.Enumerate< T>()。ToList();
}

另一方面,谁叫呼叫方法使用此代码,直到对结果求值才真正执行该方法:

  using(SqlDataReader rd = cmd.ExecuteReader() ){
while(rd.Read())
收益回报率rd.ConvertTo< T>(); //扩展方法包装FastMember
}

但是当它们执行返回的 IEnumerable<> ,打开 using 块,而不显示 Dispose(),直到 IEnumerable<> 完成其迭代为止,此时您已经从数据读取器中读取了所需的所有内容。


I've noticed something curious about reading from an IDataReader within a using statement that I can't comprehend. Though I'm sure the answer is simple.

Why is it that whilst inside the using (SqlDataReader rd) { ... } if I directly perform a yield return the reader stays open for the duration of the read. But if I perform a direct return calling a SqlDataReader extension method (outlined below) that the reader closes before the enumerable can be actualized?

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

    rd.NextResult();
}

To be absolutely clear of what I'm asking, I'm unsure why the following are fundamentally different:

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.

When your method uses yield return, the compiler actually changes the compiled code to return an IEnumerable<>, and the code in your method will not run until other code starts iterating over the returned IEnumerable<>.

That means that the code below doesn't even run the first line of your Enumerate method before it disposes the reader and returns a value. By the time someone else starts iterating over your returned IEnumerable<>, the reader has already been disposed.

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

But this code would execute the entire Enumerate() method in order to produce a List<> of results prior to returning:

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

On the other hand, whoever's calling the method with this code doesn't actually execute the method until the result is evaluated:

using(SqlDataReader rd = cmd.ExecuteReader()){
    while(rd.Read())
        yield return rd.ConvertTo<T>(); //extension method wrapping FastMember
}

But the moment they execute the returned IEnumerable<>, the using block opens up, and it doesn't Dispose() until the IEnumerable<> finishes its iterations, at which point you will have already read everything you need from the data reader.

这篇关于收益回报与收益IEnumerable T的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆