【问题标题】:The interaction between yield and LINQyield 和 LINQ 之间的交互
【发布时间】:2011-08-05 22:02:09
【问题描述】:

我正在从“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() 包含产量声明。 据我了解: - 执行 Elements() 返回一个 IEnumerable 集合,该集合实际上不包含任何项目。 - Where() 在该集合上执行,在场景后面有一个循环遍历每个项目,新项目是动态“加载”的,因为正在使用产量。 - 与 Where 语句匹配的所有项目都作为 IEnumerable 集合返回,并且物理上位于该集合中。

首先,我对上述假设是否正确? 其次,如果我是对的——如果我想返回一个“生成的”集合,而不是返回一个物理上填满所有过滤数据的集合,该怎么办? 我问这个是因为它失去了不将整个“匹配”块读入内存的全部目的,而是一次迭代一个匹配元素......

【问题讨论】:

  • 您有两个问题,它们并不真正相关。我认为您实际上应该问两个单独的问题。您可以从这篇文章中删除其中一个并为另一个创建一个新的。

标签: c# linq linq-to-xml yield


【解决方案1】:

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

当您尝试获取第一个项目时,Where() 会迭代源集合,直到找到第一个匹配的项目。因此,元素在Elements()Elements(XName) 中都进行了流式传输,并且整个集合永远不会在内存中,只是一块一块地。

【讨论】:

【解决方案2】:

Where() 在那个集合上执行 首先,我对上述假设是否正确?

没有。 Where 返回一个懒惰的IEnumerable&lt;XElement&gt;。稍后,当枚举 IEnumerable&lt;XElement&gt; 时,就会产生并过滤元素。

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

【讨论】:

    【解决方案3】:

    与 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 形式存在。您可能希望确保针对非内存流(例如直接来自文件或网络流)进行流式传输。

    【讨论】:

    • 感谢您的文章和解释,将检查它。现在对我来说一切都有意义:)
    猜你喜欢
    • 1970-01-01
    • 2012-09-06
    • 2016-11-11
    • 2016-05-15
    • 2015-03-31
    • 2014-06-28
    • 2015-07-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多