目前我正在阅读流中的一组项目。我这样做如下:
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)。
答案 0 :(得分:5)
因为LoadItems
是一个懒惰的可枚举(使用yield
)并且您将其分配给一个字段,这意味着每次枚举_items
时,您实际上都会导致LoadItems()
内的循环{1}}再次运行,即(Enumerable.Count
每次都会创建一个新Enumerator
,导致LoadItems
正文再次运行。由于您不是每次在LoadItems
内重新创建阅读器,其光标将位于流的末尾,因此可能无法再读取任何行 - 我怀疑它正在返回null
并且您在第二次调用时返回的单个Item
对象包含null
字符串。
解决这个问题的方法是通过调用LoadItems
来“实现”Enumerable.ToList
的结果,这会给你一个具体的列表:
return _items ?? (_items = LoadItems().ToList());
或者让读者回到流的开头(如果可能的话),这样LoadItems
每次都可以再次运行。
但是我建议你在这种情况下简单地摆脱yield
并返回一个具体的清单,因为没有什么好处所以你付出了复杂的价格而没有收获。
答案 1 :(得分:1)
你的变量名让你误入歧途。目前:
private IEnumerable<Item> _items;
你是懒加载并保存迭代器,而你可能想要延迟加载并保存项(如变量名所示):
public class Parser{
private TextReader _reader; //Get set in Constructor
private List<Item> _items;
public IEnumerable<Item> Items{
get{
return _items ?? (_items = LoadItems().ToList());
}
}
private IEnumerable<Item> LoadItems(){
while(_reader.Peek() >= 0){
yield return new Item(_reader.ReadLine()); //Actually it's a little different
}
}
}
答案 2 :(得分:1)
考虑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);
}
因此LoadItems()
确实只调用一次,但它返回的对象GetEnumerator()
调用了两次。
由于TextReader
已经移动,这会给你带来错误的结果。虽然请注意它会导致内存使用量低于保留所有项目,所以当你不想两次使用同一组项目时它会有好处。
由于你做想要,你需要创建一个存储它们的对象:
return _items = _items ?? _items = LoadItems().ToList();