背景:我有一堆从数据库中获取的字符串,我想返回它们。传统上,它会是这样的:
public List<string> GetStuff(string connectionString)
{
List<string> categoryList = new List<string>();
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
string commandText = "GetStuff";
using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection))
{
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlConnection.Open();
SqlDataReader sqlDataReader = sqlCommand.ExecuteReader();
while (sqlDataReader.Read())
{
categoryList.Add(sqlDataReader["myImportantColumn"].ToString());
}
}
}
return categoryList;
}
但后来我认为消费者会想要遍历这些项目而不关心其他太多,我不想把自己限制在列表中,所以如果我返回一个 IEnumerable一切都很好/灵活。所以我在想我可以使用“ yield 返回”类型的设计来处理这个......像这样:
public IEnumerable<string> GetStuff(string connectionString)
{
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
string commandText = "GetStuff";
using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection))
{
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlConnection.Open();
SqlDataReader sqlDataReader = sqlCommand.ExecuteReader();
while (sqlDataReader.Read())
{
yield return sqlDataReader["myImportantColumn"].ToString();
}
}
}
}
但现在我正在阅读更多关于 yield 的内容(在这样的网站上......msdn 似乎没有提到这一点),它显然是一个懒惰的评估器,它使 populator 的状态保持在预期状态有人要求下一个值,然后只运行它直到它返回下一个值。
在大多数情况下这似乎很好,但是对于数据库调用,这听起来有点冒险。作为一个有点做作的例子,如果有人从我从数据库调用中填充的 IEnumerable 请求,通过它的一半,然后陷入循环......据我所知我的数据库连接正在进行永远保持开放。
如果迭代器没有完成,在某些情况下听起来像是在自找麻烦……我是不是漏掉了什么?
最佳答案
这是一种平衡行为:您是想立即将所有数据强制存入内存以便释放连接,还是想从流式传输数据中获益,但代价是一直占用连接?
在我看来,这个决定可能取决于调用者,他们更了解自己想做什么。如果您使用迭代器 block 编写代码,调用者可以非常轻松地将流式格式转换为完全缓冲的格式:
List<string> stuff = new List<string>(GetStuff(connectionString));
另一方面,如果您自己进行缓冲,则调用者无法返回到流模型。
所以我可能会使用流模型并在文档中明确说明它的作用,并建议调用者做出适当的决定。您甚至可能想要提供一个辅助方法来基本上调用流版本并将其转换为列表。
当然,如果您不相信调用者会做出适当的决定,并且您有充分的理由相信他们永远不会真正想要流式传输数据(例如,它永远不会返回太多)然后去对于列表方法。无论哪种方式,记录它 - 它很可能会影响返回值的使用方式。
处理大量数据的另一种选择是使用批处理,当然 - 这与最初的问题有些不同,但在流式传输通常很有吸引力的情况下,这是一种不同的考虑方法。
关于C# IEnumerator/yield 结构可能不好?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/803878/