F#收益!operator - 实现和可能的C#等价物

Joh*_*lph 15 c# ienumerable f# yield enumerators

我正在学习F#,我真的很喜欢yield!(yield-bang)运算符.不仅因为它的名字,而且它当然也是它的作用.

yield!操作者基本上可以让你产生序列的所有元素从一个序列的表达.这对于编写枚举器很有用.由于我经常遇到大而复杂的调查员,我对策略很感兴趣,我们可以用它来分解它们,并从简单的枚举器中编写它们.

不幸的是,yield!C#中没有运营商.据我了解,它的作用就像一foreach (var x in source) yield x;本书,但我正在阅读的书(Petricek的真实世界F# - Manning)表明它有更好的表现......

  • 那么F#编译器到底在做什么呢?(是的,我也可以使用Reflector来查看它,但我希望对机制有更详细的描述).

为了在C#中实现类似的构造,我已经探索了多种方式,但是它们都没有像yield!运算符那样简洁,我也不确定它们的复杂性.如果我的BigO号码正确,有人可以提供输入吗?

  • 将枚举器分解为多个私有枚举器,然后从公共枚举器中生成每个元素:

    foreach (var x in part1()) yield x
    foreach (var x in part2()) yield x
    
    Run Code Online (Sandbox Code Playgroud)

    这将有效地导致每个元素的"双倍收益".那是O(2n)吗?(或者可能更糟?)无论如何,使用这种方法阻止我使用yield break;我的任何子部分.

  • 将枚举器分解为多个私有枚举器,然后从公共枚举器中连接所有私有枚举器:

    return part1().Concat(part2())
    
    Run Code Online (Sandbox Code Playgroud)

    我相信这与上述解决方案没有什么不同,因为它Concat()是按照我上面概述的方式实现的.

还有其他选择吗?

Tho*_*que 7

在当前版本的C#中,我认为除了foreach... yield return和之外你还有其他选择Concat.我同意yield!在C#中使用运算符会很好,它会使某些结构更加优雅,但我怀疑这个功能是否会成为"必备"列表,因为我们可以很容易地将其删除.

您可能对此MS研究论文感兴趣,该论文介绍了一种新的yield foreach构造:

IEnumerable<XmlNode> Traverse(XmlNode n)
{
    yield return n;
    foreach (XmlNode c in n.ChildNodes)
        yield foreach Traverse(c);
}
Run Code Online (Sandbox Code Playgroud)

关于复杂性的问题:在两种情况下都是O(n).不使用O(2n),因为它表示与O(n)(线性)相同的复杂度.我认为你不能用当前的C#功能做得更好......


kvb*_*kvb 6

关于编译器如何翻译yield!操作,Thomas Levesque在其答案中引用的论文说明了4.3节中的一种实现技术(特别是,他们的示例跨越图7-9说明了一般策略).我不认为在C#中的迭代器块中有任何好方法可以做到这一点 - 因为我理解你提出的解决方案,它们在递归使用时都会导致二次行为.您总是可以手动创建NestedEnumerable<T>子类来实现性能优势,但与使用普通迭代器块相比,这将非常难看.