C# 收益率总是被调用
目前,我正在从流中读取一组项目。我的做法如下:C# 收益率总是被调用,c#,ienumerable,yield,C#,Ienumerable,Yield,目前,我正在从流中读取一组项目。我的做法如下: public class Parser{ private TextReader _reader; //Get set in Constructor private IEnumerable<Item> _items; public IEnumerable<Item> Items{ get{ //I >>thought<< this would prevent LoadItem
public class Parser{
private TextReader _reader; //Get set in Constructor
private IEnumerable<Item> _items;
public IEnumerable<Item> Items{
get{
//I >>thought<< this would prevent LoadItems() from being called twice.
return _items ?? (_items = LoadItems());
}
}
public IEnumerable<Item> LoadItems(){
while(_reader.Peek() >= 0){
yield return new Item(_reader.ReadLine()); //Actually it's a little different
}
}
}
var textReader = //Load textreader here
var parser = new Parser(textReader);
var result1 = parser.Items.Count();
var result2 = parser.Items.Count();
现在result1
是2,而result2
是1
现在我注意到,我的空检查是无用的?似乎每次我调用该函数时,它都会被生成
有人能解释一下为什么会这样吗?对于这种情况,最好的解决方案是什么(请告诉我,我所做的是否完全是废话:P)。
因为
LoadItems
是一个惰性的可枚举项(使用yield
),并且您将其分配给一个字段,这意味着每次枚举\u items
时,实际上是在LoadItems()中引起循环
再次运行,即(Enumerable.Count
每次创建一个新的Enumerator
,导致LoadItems
正文再次运行)。由于每次在LoadItems
中都没有重新创建读卡器,因此它的光标将位于流的末尾,因此可能无法读取更多的行-我怀疑它正在返回null
,并且在第二次调用中返回的单个Item
对象包含null
绳子
解决方法是通过调用Enumerable.ToList
来“实现”加载项的结果,这将为您提供一个具体的列表:
return _items ?? (_items = LoadItems().ToList());
或者将读取器返回到流的开头(如果可能),以便LoadItems
每次都可以以相同的方式再次运行
但我建议您在这种情况下,只需去掉
收益率,并返回一个具体的列表,因为它没有什么好处,所以您付出了复杂的代价,却没有任何收益。您的变量名将您引入歧途。目前:
private IEnumerable<Item> _items;
私有IEnumerable\u项;
您正在延迟加载和保存迭代器,而您可能希望延迟加载和保存项(如变量名称所示):
公共类解析器{
私有TextReader _reader;//在构造函数中设置
私人物品清单;
公共数字项目{
得到{
返回_items???(_items=LoadItems().ToList());
}
}
私有IEnumerable LoadItems(){
而(_reader.Peek()>=0){
yield return new Item(_reader.ReadLine());//实际上有点不同
}
}
}
考虑一下yield
用作速记。您的代码将转换为以下内容:
private class <>ImpossibleNameSoItWontCollide : IEnumerator<Item>
{
private TextReader _rdr;
/* other state-holding fields */
public <>ImpossibleNameSoItWontCollide(TextReader rdr)
{
_rdr = rdr;
}
/* Implement MoveNext, Current here */
}
private class <>ImpossibleNameSoItWontCollide2 : IEnumerable<Item>
{
private TextReader _rdr;
/* other state-holding fields */
public <>ImpossibleNameSoItWontCollide2(TextReader rdr)
{
_rdr = rdr;
}
public <>ImpossibleNameSoItWontCollide GetEnumerator()
{
return new <>ImpossibleNameSoItWontCollide(_rdr);
}
/* etc */
}
public IEnumerable<Item> LoadItems()
{
return new <>ImpossibleNameSoItWontCollide2(_rdr);
}
是的,它应该每次都会产生,因为您没有在任何地方分配“\u items”变量,所以每次都会调用它的始终为null和load items。如果我错了,请纠正我,但我不是在这里分配它\u items=LoadItems()
,我就是这么想的。但是,当您阅读一个资源并返回项目时。如何确保不丢失资源。您可以通过在结果IEnumerable
上调用Enumerable.ToList
或手动构造列表
(或其他数据结构),例如LoadItems().ToList(),将读取的行实现为一个具体的列表,从而保留读取器的结果
或新列表(LoadItems())
。啊,谢谢!我完全把收益率的概念搞错了!谢谢你的解释:)啊,太棒了,我真的很喜欢这个答案,因为你解释了yield的语法糖实际上是做什么的!不客气。值得补充的是,如果您在完成后将using
块放入LoadItems
中以处理文本阅读器
,则枚举器的dispose()
方法将处理它(但不是枚举,因此如果枚举没有实际枚举,则不会调用它)。
private class <>ImpossibleNameSoItWontCollide : IEnumerator<Item>
{
private TextReader _rdr;
/* other state-holding fields */
public <>ImpossibleNameSoItWontCollide(TextReader rdr)
{
_rdr = rdr;
}
/* Implement MoveNext, Current here */
}
private class <>ImpossibleNameSoItWontCollide2 : IEnumerable<Item>
{
private TextReader _rdr;
/* other state-holding fields */
public <>ImpossibleNameSoItWontCollide2(TextReader rdr)
{
_rdr = rdr;
}
public <>ImpossibleNameSoItWontCollide GetEnumerator()
{
return new <>ImpossibleNameSoItWontCollide(_rdr);
}
/* etc */
}
public IEnumerable<Item> LoadItems()
{
return new <>ImpossibleNameSoItWontCollide2(_rdr);
}
return _items = _items ?? _items = LoadItems().ToList();