产量与LINQ的相互作用

本文关键字:相互作用 LINQ | 更新日期: 2023-09-27 18:04:44

我正在阅读一段来自"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()来过滤它的结果,但我对这里的执行顺序很感兴趣,因为Elements()包含yield语句。据我所知:-执行Elements()返回一个IEnumerable集合,这个集合物理上还不包含任何项。-在集合上执行()的地方,在场景后面有一个循环遍历每个项目,新项目被"加载"在飞行中,因为使用了yield。-所有匹配Where语句的项作为IEnumerable集合返回,并且在物理上属于该集合。

首先,我的假设正确吗?其次,如果我是对的-如果我想返回一个"产生"的集合,而不是返回一个集合,这是充满了所有的过滤数据的物理?我问这个,因为它失去了整个目的不是读取整个"匹配"块到内存中,但迭代一个匹配元素一次…

产量与LINQ的相互作用

我认为当您说项目物理上在集合中时,您的意思是内存中有一个结构包含所有项目现在。对于Where(),情况并非如此,它在内部也使用yield(或与yield作用相同的东西)。

当您尝试获取第一项时,Where()迭代源集合,直到找到第一个匹配的项。所以,元素在Elements()Elements(XName)中都是流的,整个集合从来不在内存中,只是一块一块地在内存中。

Where()在该集合上执行首先,我的假设是否正确?

。Where返回一个惰性IEnumerable<XElement>。稍后,当枚举该IEnumerable<XElement>时,将生成并过滤这些元素。

如果枚举该惰性IEnumerable的对象恰好收集元素(比如调用ToList),那么此时所有元素都在内存中。如果枚举lazy IEnumerable的东西碰巧一次只处理一个项目(例如foreach循环,它不保留对XElement的引用),那么一次只有一个项目将在内存中。

所有匹配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,那么整个枚举将被求值并复制到列表中,因此要小心Where返回的IEnumerable所做的操作。

还请记住,如果readerFactory返回的读取器是从内存中读取的(例如StringReader),那么文档将物理地存在于内存中—除非枚举它们,否则不会有任何DOM节点实例。一旦枚举了这些元素,文档将在内存中存在两次,一次用于原始文档,一次以DOM形式存在。你可能想要确保你的流是针对非内存流完成的(例如,直接从文件或网络流)。