16

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

yield!运算符基本上允许您从序列表达式中生成序列的所有元素。这对于组成枚举数很有用。因为我经常遇到大而复杂的枚举器,所以我对我们可以用来分解它们并从更简单的枚举器组合它们的策略感兴趣。

不幸的是,该yield!运算符在 C# 中不可用。据我了解,它的作用类似于foreach (var x in source) yield x;但我正在阅读的书(Petricek's Real World F# - Manning)表明它具有更好的性能......

  • 那么 F# 编译器到底做了什么?(是的,我也可以使用 Reflector 来查看它,但我想对该机制进行更详细的描述)。

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

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

    foreach (var x in part1()) yield x
    foreach (var x in part2()) yield x
    

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

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

    return part1().Concat(part2())
    

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

还有其他选择吗?

4

3 回答 3

7

在当前版本的 C# 中,我认为除了foreach... yield returnand之外没有其他选择Concat。我同意yield!在 C# 中使用运算符会很好,它会使某些结构更加优雅,但我怀疑此功能是否会进入“必备”列表,因为我们可以轻松地做到没有它。

您可能对这篇MS 研究论文感兴趣,它介绍了一种新yield foreach结构:

IEnumerable<XmlNode> Traverse(XmlNode n)
{
    yield return n;
    foreach (XmlNode c in n.ChildNodes)
        yield foreach Traverse(c);
}

关于您关于复杂性的问题:在这两种情况下都是O(n)不使用O(2n) ,因为它表示与O(n)(线性)相同的复杂度。我认为使用当前的 C# 功能没有比这更好的了……

于 2010-08-17T08:35:36.070 回答
6

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

于 2010-08-17T13:36:01.013 回答
4

yield!在 C#中没有直接对应物。您目前遇到了foreach和的组合yield return

但是,IIRC、LINQ 提供了类似的东西,即SelectMany查询运算符,它转换为 C# 作为多个from .. in ..子句。

(我希望我不会混淆两个不同的概念,但 IIRC,两者yield!本质SelectMany上都是“扁平化”投影;即对象的层次结构被“扁平化”成一个列表。)

于 2010-08-17T08:41:25.710 回答