12

当我有代码块时

static void Main()
{

  foreach (int i in YieldDemo.SupplyIntegers())
  {
    Console.WriteLine("{0} is consumed by foreach iteration", i);
  }
}


 class YieldDemo
  {
    public static IEnumerable<int> SupplyIntegers()
     {
         yield return 1;
         yield return 2;
          yield return 3;
       }
   }

我可以将收益回报背后的原理解释为

  1. Main() 调用 SupplyIntegers()
  2. |1| |2| |3| are stored in contiguous memory block.Pointer of "IEnumerator" Moves to |1|
  3. 控制从 SupplyInteger() 返回到 Main()。
  4. Main() 打印值
  5. 指针移动到 |2|,依此类推。

澄清:

(1) 通常我们会在一个函数中允许一个有效的 return 语句。当存在多个 yield return ,yield return,... 语句时,C#如何处理?

(2)一旦遇到return,就没有办法控制再次回到SupplyIntegers(),如果允许,Yield不会再次从1开始吗?我的意思是收益率回报1?

4

4 回答 4

34

不——远非如此;我给你写个长手版……太邋遢了!


请注意,如果您了解foreach实际情况,它也会有所帮助:

using(var iterator = YieldDemo.SupplyIntegers().GetEnumerator()) {
    int i;
    while(iterator.MoveNext()) {
        i = iterator.Current;
         Console.WriteLine("{0} is consumed by foreach iteration", i);
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
static class Program
{
    static void Main()
    {

        foreach (int i in YieldDemo.SupplyIntegers())
        {
            Console.WriteLine("{0} is consumed by foreach iteration", i);
        }
    }
}

 class YieldDemo
  {

    public static IEnumerable<int> SupplyIntegers()
     {
         return new YieldEnumerable();
       }
    class YieldEnumerable : IEnumerable<int>
    {
        public IEnumerator<int> GetEnumerator()
        {
            return new YieldIterator();
        }
        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
    }
    class YieldIterator : IEnumerator<int>
    {
        private int state = 0;
        private int value;
        public int Current { get { return value; } }
        object IEnumerator.Current { get { return Current; } }
        void IEnumerator.Reset() { throw new NotSupportedException(); }
        void IDisposable.Dispose() { }
        public bool MoveNext()
        {
            switch (state)
            {
                case 0: value = 1; state = 1;  return true;
                case 1: value = 2; state = 2;  return true;
                case 2: value = 3; state = 3; return true;
                default: return false;
            }
        }
    }
}

如您所见,它在迭代器中构建了一个状态机,状态机前进了MoveNext. 我已经将模式与state字段一起使用,您可以看到这对于更复杂的迭代器是如何工作的。

重要的:

  • 迭代器块中的任何变量都成为状态机上的字段
  • 如果你有一个finally块(包括using),它会进入Dispose()
  • 导致 ayield return变成 a 的部分代码case(大致)
  • yield break成为state = -1; return false;(或类似的)

C# 编译器执行此操作的方式非常复杂,但它使编写迭代器变得轻而易举。

于 2009-10-20T19:44:57.163 回答
3

它只是一个语法糖,.net 为您生成 IEnumerator 类并实现 MoveNext、Current 和 Reset 方法,然后生成返回该 IEnumerator 的 IEnumarable 类 GetEnumerator,您可以通过 .net 反射器或 ildasm 看到魔术类。

也见这里

于 2009-10-20T19:47:34.267 回答
2

简而言之,迭代器块(或带有yield语句的方法,如果可能的话)由编译器转换为编译器生成的类。此类实现IEnumerator并将yield语句转换为该类的“状态”。

例如,这个:

yield return 1;
yield return 2;
yield return 3;

可能会变成类似的东西:

switch (state)
{
    case 0: goto LABEL_A;
    case 1: goto LABEL_B;
    case 2: goto LABEL_C;
}
LABEL_A:
    return 1;
LABEL_B:
    return 2;
LABEL_C:
    return 3;

迭代器块可以看作是抽象的状态机。此代码将由IEnumerator的方法调用。

于 2009-10-20T19:56:27.733 回答
1

简而言之,(当你在等待 marc 的长手版本时)当编译器看到 yield 语句时,它会在幕后为您构建一个自定义类的新实例,该实例实现一个名为 的接口IEnumerator,该接口具有方法Current(),和MoveNext(),并保持跟踪您当前在迭代过程中的位置...在上面的示例中,它还将跟踪要枚举的列表中的值。

于 2009-10-20T19:52:16.853 回答