我注意到在 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
}
摘要:该方法的两个版本都延迟,但由于
ReadSomeProcExt
不会延迟执行,因此读取器在执行传递回调用方之前(即在Enumerate<T>
可以运行之前(被释放。 另一方面,ReadSomeProc
在将读取器传递回调用方之前不会创建读取器,因此在读取其所有值之前,它不会释放容器。
当你的方法使用yield return
时,编译器实际上会更改编译后的代码以返回一个IEnumerable<>
,并且你的方法中的代码不会运行,直到其他代码开始迭代返回的IEnumerable<>
。
这意味着下面的代码在释放读取器并返回值之前甚至不会运行Enumerate
方法的第一行。当其他人开始迭代您返回的IEnumerable<>
时,读取器已经被处置了。
using(SqlDataReader rd = cmd.ExecuteReader()){
return rd.Enumerate<T>();
}
但是此代码将执行整个Enumerate()
方法,以便在返回之前生成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
}
但是当他们执行返回的IEnumerable<>
时,using
块打开,直到IEnumerable<>
完成迭代才会Dispose()
,此时您已经从数据读取器读取了所需的一切。
这是因为"yield return"将返回一个元素并继续迭代,而"正常"返回将完成调用。