我正在阅读“XStreamingReader”库中的一段代码(这对于能够在XML文档上执行LINQ查询而不将实际文档加载到内存中(如在XDocument对象中)似乎是一个非常酷的解决方案 并且想知道以下事项:
public IEnumerable<XElement> Elements()
{
using (var reader = readerFactory())
{
reader.MoveToContent();
MoveToNextElement(reader);
while (!reader.EOF)
{
yield return XElement.Load(reader.ReadSubtree());
MoveToNextFollowing(reader);
}
}
}
public IEnumerable<XElement> Elements(XName name)
{
return Elements().Where(x => x.Name == name);
}
关于第二种方法Elements(XName)
- 该方法首先调用Elements(),然后使用Where()
来过滤它的结果,但是我对这里的执行顺序很感兴趣()包含一个yield语句。
据我所知:
- Executing Elements()返回一个IEnumerable集合,此集合实际上不包含任何项目YET。
- 在该集合上执行Where(),在场景后面有一个遍历每个项目的循环,因为正在使用yield,所以新项目在运行中被“加载”。
- 与Where语句匹配的所有项目都作为IEnumerable集合返回,并且在该集合中是物理的。
首先,我是否正确上述假设? 第二,如果我是对的 - 如果我想返回一个“已屈服”的集合而不是返回一个用所有过滤数据填充的集合,该怎么办? 我问这个是因为它失去了整个目的,即不将整个“匹配”块读入内存,而是一次迭代一个匹配元素......
答案 0 :(得分:2)
我假设当你说物品在物品集合中时,你的意思是内存中有一个结构,其中包含现在的所有物品。对于Where()
,情况并非如此,它内部使用yield
(或者与yield
的行为相同)。
当您尝试获取第一个项目时,Where()
会迭代源集合,直到找到匹配的第一个项目。因此,元素在Elements()
和Elements(XName)
中流式传输,整个集合永远不会在内存中,只是一片一片。
答案 1 :(得分:1)
在该集合上执行Where() 首先,我是否正确上述假设?
没有。哪里返回一个懒惰的IEnumerable<XElement>
。稍后,当枚举IEnumerable<XElement>
时,将生成并过滤元素。
如果枚举懒惰IEnumerable的东西恰好收集元素(例如对ToList的调用),那么所有元素都将在内存中。如果枚举惰性IEnumerable的事物恰好一次处理一个项目(例如foreach循环,它不保留对XElement的引用),那么一次只有一个项目将存在于内存中。
答案 2 :(得分:0)
与Where语句匹配的所有项目都作为IEnumerable集合返回,并且在该集合中是物理的。首先,我是否正确上述假设?
没有。 Where
在内部实现了一个额外的枚举器,它可以执行您希望它执行的操作。如果未枚举IEnumerable
,则永远不会调用读者,并且永远不会创建单个XElement
实例,并且永远不会运行过滤代码。
请参阅Jon Skeet关于重新实现Where
子句行为的文章:http://msmvps.com/blogs/jon_skeet/archive/2010/09/03/reimplementing-linq-to-objects-part-2-quot-where-quot.aspx。他模仿现有的实现(出于解释目的 - 不需要在实际代码中使用他的重新实现),并且他的代码使用yield return
。
请注意,如果您调用ToList
,则会评估整个枚举并将其复制到列表中,因此请小心 与IEnumerable
Where
返回。
还要记住,如果readerFactory
返回的阅读器正在从内存中读取(例如StringReader
),那么文档将在物理上存在于内存中 - 不会有任何DOM实例节点直到你枚举它们。一旦枚举了这些元素,您的文档将在内存中存在两次,一个用于原始文档,一个用于DOM格式。您可能希望确保您的流式传输是针对非内存流完成的(例如,直接来自文件或网络流)。