【问题标题】:Yield return inside usings使用内的收益回报
【发布时间】:2011-08-08 15:36:33
【问题描述】:

如果我没记错的话,当我在 using SqlConnection 块中使用 yield 时,我遇到了运行时异常。

using (var connection = new SqlConnection(connectionString))
{
    var command = new SqlCommand(queryString, connection);
    connection.Open();

    SqlDataReader reader = command.ExecuteReader();

    // Call Read before accessing data.
    while (reader.Read())
    {
        yield reader[0];
    }

    // Call Close when done reading.
    reader.Close();
}

当我将 yield 替换为一个 List 时,这些问题得到了解决,我在每次迭代中都添加了项目。

using StreamReader 块内时,我还没有发生同样的问题

using (var streamReader = new StreamReader(fileName))
{
    string line;
    while ((line = streamReader.ReadLine()) != null)
    {
        yield return line;
    }
}

有什么解释为什么在前一种情况下发生异常而不是在后一种情况下发生异常?这种结构是否可取?

编辑要得到我过去所做的错误(早期处理),您应该调用下面的第一个方法:

IEnumerable<string> Read(string fileName)
{
    using (var streamReader = new StreamReader(fileName))
    {
        return Read(streamReader);
    } // Dispose will be executed before ReadLine() because of deffered execution
}

IEnumerable<string> Read(StreamReader streamReader)
{
    string line;
    while ((line = streamReader.ReadLine()) != null)
    {
        yield return line;
    }
}

使用其他延迟执行的方式也可以实现相同的错误,例如System.Linq.Enumerable.Select()

【问题讨论】:

  • 你从 sql yield 得到什么异常?
  • 如果您有反射器,请查看两个代码示例生成的代码。我应该阐明为什么一个投掷而一个不投掷。
  • @rossisdead 正如 LukeH 所指出的,一个可能的异常是 TimeoutException
  • @rossisdead 正如 jamietre 指出的那样,问题不是我在问题中编写代码时出现的问题,而是在我重构它时出现的问题,如我的问题编辑中所示

标签: c# .net exception using yield-return


【解决方案1】:

请参阅 this post 以获得对 usingyield 问题的详细解释。因为您在枚举器中返回,所以 using 块在访问任何内容之前已经破坏了上下文。答案有很好的解决方案,基本上,要么将包装器方法设为枚举器,要么构建一个列表。

另外,在阅读器周围使用using 而不是连接通常更实用,并使用CommandBehavior.CloseConnection 确保在阅读器完成后释放资源。尽管在您的情况下并不重要,但如果您从方法返回数据读取器,这将确保在释放读取器时正确关闭连接。

   using(SqlDataReader reader = 
             command.ExecuteReader(CommandBehavior.CloseConnection)) {
        while (reader.Read())
        {
            yield reader[0];
        }
   }

【讨论】:

  • 你是对的。如果using 指令不在同一方法中,yield returns 可能会遇到早期处置。这可能是我发布这个问题时隐约记得的例外情况。
  • 在这种情况下,我会将using 应用于阅读器连接。
  • 我同意这是最佳做法,但 SQL 连接是一种特殊情况,因为创建数据读取器并将其传递到上下文之外是一种正常做法(CommandBehavior.CloseConnection 支持)它是创建的。连接上的Dispose 只需调用Close(并删除连接字符串)。所以从这个意义上说,在连接周围使用using 并不是真的必要,而且许多数据库管理子系统的创建方式也不实用。
  • 根据经验,我应该说:永远不要在使用 yield return 或其他延迟执行方法的方法中接受 IDisposable 参数
【解决方案2】:

编译器应该在这两种情况下正确处理using 块内的yield。没有明显的理由应该引发异常。

需要注意的一点是,只有在您完成迭代和/或手动释放枚举器对象后,才会释放连接。如果您在公共方法中公开此代码,那么愚蠢或恶意代码可能会使您的连接长时间保持打开状态:

 var enumerable = YourMethodThatYieldsFromTheDataReader();
 var enumerator = enumerable.GetEnumerator();
 enumerator.MoveNext();
 Thread.Sleep(forever);    // your connection will never be disposed

【讨论】:

  • 这是有道理的。一种可能的异常是 TimeoutException。
猜你喜欢
  • 1970-01-01
  • 2011-01-13
  • 2011-05-27
  • 2010-09-29
  • 1970-01-01
  • 2012-01-14
  • 1970-01-01
  • 2018-01-23
相关资源
最近更新 更多