代码之家  ›  专栏  ›  技术社区  ›  CoderDennis

在C#中使用收益返回迭代器的目的/优点是什么?

  •  72
  • CoderDennis  · 技术社区  · 15 年前

    yield return x; 在C#方法中,也可以用同样的方法返回整个列表。在这些情况下,使用 yield return

    另外,在什么类型的场景中 生利 你不能直接返回完整的列表吗?

    10 回复  |  直到 15 年前
        1
  •  111
  •   CoderDennis    15 年前

    但是如果你自己也在收集呢?

    通常,迭代器可用于 惰性地生成一系列对象 Enumerable.Range 方法内部没有任何类型的集合。它只生成下一个数字 随需应变 . 使用状态机生成延迟序列有很多用途。他们中的大多数人都在保险范围内 函数式编程概念 .

    在我看来,如果您将迭代器视为枚举集合的一种方式(这只是最简单的用例之一),那么您就错了。正如我所说,迭代器是返回序列的方法。这个序列甚至可能是 极大的 . 无法返回无限长的列表并使用前100项。信息技术 有时懒惰。 返回集合与返回集合生成器有很大不同 (这就是迭代器)。这是把苹果和桔子做比较。

    static IEnumerable<int> GetPrimeNumbers() {
       for (int num = 2; ; ++num) 
           if (IsPrime(num))
               yield return num;
    }
    
    static void Main() { 
       foreach (var i in GetPrimeNumbers()) 
           if (i < 10000)
               Console.WriteLine(i);
           else
               break;
    }
    

    本例打印小于10000的素数。您可以轻松地将其更改为打印少于一百万的数字,而无需接触素数生成算法。在本例中,您无法返回所有素数的列表,因为序列是无限的,消费者甚至不知道从一开始需要多少项。

        2
  •  24
  •   Ray    15 年前

    yield return 是吗 ; 清单可能很昂贵。(另外,过一段时间,你会发现它们笨重且不雅观。)

    但是如果你没有一份清单呢?

    生利 允许您遍历 数据结构 (不一定列出)以多种方式。例如,如果对象是一棵树,则可以按前序或后序遍历节点,而无需创建其他列表或更改基础数据结构。

    public IEnumerable<T> InOrder()
    {
        foreach (T k in kids)
            foreach (T n in k.InOrder())
                yield return n;
        yield return (T) this;
    }
    
    public IEnumerable<T> PreOrder()
    {
        yield return (T) this;
        foreach (T k in kids)
            foreach (T n in k.PreOrder())
                yield return n;
    }
    
        3
  •  15
  •   Community CDub    7 年前

    延迟计算/延迟执行

    任何 直到您实际调用该特定结果为止。这意味着它们也可以有效地链接在一起。突击测验:以下代码将在文件上迭代多少次?

    var query = File.ReadLines(@"C:\MyFile.txt")
                                .Where(l => l.Contains("search text") )
                                .Select(l => int.Parse(l.SubString(5,8))
                                .Where(i => i > 10 );
    
    int sum=0;
    foreach (int value in query) 
    {
        sum += value;
    }
    

    答案恰恰是一个,而且要到下一个月才能找到答案 foreach 环即使我有三个独立的linq操作符函数,我们仍然只循环一次文件的内容。

    这除了性能之外还有其他好处。例如,我可以写一个相当简单的 通用的 方法读取和预筛选日志文件一次,并在几个不同的位置使用相同的方法,每次使用都会添加不同的筛选器。因此,我在有效地重用代码的同时保持了良好的性能。

    无限列表


    C# fibonacci function returning errors

    基本上,我使用一个迭代器块来实现斐波那契序列,该迭代器块永远不会停止(至少在到达MaxInt之前不会停止),然后以安全的方式使用该实现。

    这是其中一件用散文来解释比用简单的视觉来解释更难的事情 1. :

    Imperative vs Functional Separation of Concerns

    如果您看不到该图像,它将显示相同代码的两个版本,并针对不同的关注点突出显示背景。linq代码将所有颜色很好地分组,而传统的命令式代码将颜色混合在一起。作者认为(我也同意)这个结果是使用linq和使用命令式代码的典型结果。。。linq能够更好地组织代码,使代码在各个部分之间具有更好的流。


    1. 我认为这是原始来源: https://twitter.com/mariofusco/status/571999216039542784 . 还要注意的是,这段代码是Java,但C#与之类似。

        4
  •  10
  •   SPIRiT_1984    15 年前

    有时,您需要返回的序列太大,无法放入内存中。例如,大约3个月前,我参加了一个在MS SLQ数据库之间进行数据迁移的项目。数据以XML格式导出。 收益率 XmlReader . 它使编程变得相当容易。例如,假设一个文件有1000个 顾客 元素—如果您只是将此文件读入内存,则需要同时将所有元素存储在内存中,即使它们是按顺序处理的。因此,可以使用迭代器逐个遍历集合。在这种情况下,您只需为一个元素花费内存。

    XmlReader 因为我们的项目是让应用程序工作的唯一方法——它工作了很长时间,但至少它没有挂起整个系统,也没有提高效率 OutOfMemoryException XmlReader 没有生成迭代器。但是迭代器让我的生活变得更加轻松(我不会那么快、毫无困难地编写导入代码)。看这个 page

        5
  •  9
  •   Dan Davies Brackett    15 年前

    在玩具/演示场景中,没有太大区别。但在某些情况下,生成迭代器是有用的——有时,整个列表不可用(例如,streams),或者该列表的计算成本很高,不太可能需要整个列表。

        6
  •  2
  •   nilamo    15 年前

        7
  •  2
  •   JP Alioto    15 年前

    看看Eric White的博客(顺便说一句,非常棒的博客)上的讨论 lazy versus eager evaluation .

        8
  •  2
  •   AgileJon    15 年前

    使用 yield return 您可以迭代项目,而无需构建列表。如果您不需要该列表,但希望迭代某些项集,那么编写该列表会更容易

    foreach (var foo in GetSomeFoos()) {
        operate on foo
    }
    

    foreach (var foo in AllFoos) {
        if (some case where we do want to operate on foo) {
            operate on foo
        } else if (another case) {
            operate on foo
        }
    }
    

    您可以使用yield-returns将所有用于确定是否要对foo进行操作的逻辑放在方法内部,这样foreach循环可以更加简洁。

        9
  •  2
  •   Community CDub    7 年前

    以下是我之前对同一个问题的公认答案:

    Yield keyword value added?

    看待迭代器方法的另一种方式是,它们努力将算法“由内而外”地转换。考虑解析器。它从流中提取文本,查找其中的模式,并生成内容的高级逻辑描述。

    现在,作为解析器的作者,我可以通过采用SAX方法来简化这一过程,在SAX方法中,我有一个回调接口,每当我找到模式的下一部分时都会通知它。因此,在SAX的情况下,每次我找到一个元素的开头时,我调用 beginElement 方法等等。

    但这给我的用户带来了麻烦。它们必须实现处理程序接口,因此必须编写一个响应回调方法的状态机类。这很难做到,所以最简单的方法是使用一个构建DOM树的stock实现,这样他们就可以方便地遍历树。但随后整个结构都被缓冲到内存中,这并不好。

    但是我把解析器写成迭代器方法怎么样?

    IEnumerable<LanguageElement> Parse(Stream stream)
    {
        // imperative code that pulls from the stream and occasionally 
        // does things like:
    
        yield return new BeginStatement("if");
    
        // and so on...
    }
    

    这并不比回调接口方法更难编写——只需返回一个从我的 LanguageElement

    用户现在可以使用foreach循环我的解析器的输出,因此他们得到了一个非常方便的命令式编程接口。

    看起来他们控制住了 ,因此更容易书写和理解。

        10
  •  2
  •   Tapas Ranjan Singh    12 年前

    使用yield的基本原因是它自己生成/返回一个列表。我们可以使用返回的列表进行进一步迭代。