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();