C# 迭代器 Iterator

背景

由于枚举器存在遍历二叉树不方便的问题。才有了迭代器。在了解了迭代器之前必须了解枚举器。

迭代器就是带了状态机的枚举器。

迭代器简介

至此,你已了解 foreach 的内部实现代码,是时候了解如何使用迭代器创建 IEnumerator<T>、IEnumerable<T> 和自定义集合对应的非泛型接口的自定义实现代码了。迭代器提供明确的语法,用于指定如何迭代集合类中的数据,尤其是使用 foreach 循环。这样一来,集合的最终用户就可以浏览其内部结构,而无需知道相应结构。

枚举模式存在的问题是,手动实现起来不方便,因为必须始终指示描述集合中的当前位置所需的全部状态。对于列表集合类型类,指示这种内部状态可能比较简单;当前位置的索引就足够了。相比之下,对于需要递归遍历的数据结构(如二叉树),指示状态可能就会变得相当复杂。为了减少实现此模式所带来的挑战,C# 2.0 新增了 yield 上下文关键字,这样类就可以更轻松地决定 foreach 循环如何迭代其内容。

 

语法信息

1、迭代器可用作一种方法,或一个 get 访问器。 不能在事件、实例构造函数、静态构造函数或静态终结器中使用迭代器。
2、必须存在从 yield return 语句中的表达式类型到迭代器返回的 IEnumerable<T> 类型参数的隐式转换。
3、在 C# 中,迭代器方法不能有任何 in、ref 或 out 参数。
4、在 C# 中,yield 不是保留字,只有在 return 或 break 关键字之前使用时才有特殊含义。

迭代器块

迭代器块是有一个或多个yield语句的代码块。下面3种类型的代码块中的任意一种都可以是迭代器块:

  • 方法主体
  • 访问器主体
  • 运算符主体

迭代器块与其他代码块不同。其他块包含的语句被当做命令式。即先执行代码块中的第一个语句,然后执行后面的语句,最后控制离开块。
另一方面,迭代器块不是需要在同一时间执行的一串命令式命令,而是描述了希望编译器为我们创建的枚举器类的行为。迭代器块中的代码描述了如何枚举元素。
迭代器块由两个特殊语句:

  • yield return语句指定了序列中返回的下一项
  • yield break语句指定在序列中没有的其他项

编译器得到有关枚举项的描述后,使用它来构建包含所有需要的方法和属性实现的枚举器类。结果类被嵌套包含在迭代器声明的类中。
如下图所示,根据迭代器块的返回类型,你可以让迭代器产生枚举器或可枚举类型。 

C# 迭代器 Iterator

 

 

使用迭代器来创建枚举器

class MyClass
{
    //该类目前已实现GetEnumerator()使类本身可枚举
    public IEnumerator<string> GetEnumerator()//迭代器
    {
        yield return "black";
        yield return "gray";
        yield return "white";
    }
}
class Program
{
    static void Main()
    {
        var mc = new MyClass();
        foreach (string shade in mc)////该类目前已实现GetEnumerator()使类本身可枚举 所以 写mc
        {
            Console.WriteLine(shade);
        }
    }
}

 

反编译IL代码

C# 迭代器 Iterator

 

 

 

C# 迭代器 Iterator

 

 

 

下图演示了MyClass的代码及产生的对象。注意编译器为我们自动做了多少工作。

  • 图左的迭代器代码演示了它的返回类型是IEnumerator<string>
  • 图右演示了它有一个嵌套类实现了IEnumerator<string>

 

 我们发现show类,内部自动生成一个枚举器,该枚举带了状态机的功能。并且自动继承了IEnumerator<object>, IEnumerator, IDisposable三个接口。

 IEnumerator<T> 和 IEnumerator 接口的类图

C# 迭代器 Iterator

 

 

 

至此我们一目了然,迭代器就是带了状态机的枚举器。

 使用迭代器来创建可枚举类型

之前示例创建的类包含两部分:产生返回枚举器方法的迭代器以及返回枚举器的GetEnumerator方法。
本节例子中,我们用迭代器来创建可枚举类型,而不是枚举器。与之前的示例相比,本例有以下不同:

  • 若实现GetEnumerator,让它调用迭代器方法以获取自动生成的实现IEnumerable的类实例。然后从IEnumerable对象返回由GetEnumerator创建的枚举器,如图右
  • 若通过不实现GetEnumerator使类本身不可枚举,仍然可以使用由迭代器返回的可枚举类,只需要直接调用迭代器方法

本例中,BlackAndWhite迭代器方法返回IEnumerable<string>而不是IEnumerator<string>。因此MyClass首先调用BlackAndWhite方法获取它的可枚举类型对象,然后调用对象的GetEnumerator方法来获取结果,从而实现GetEnumerator方法

 

class MyClass
{
    //该类目前未实现 GetEnumerator()
    /*  public IEnumerator<string> GetEnumerator()
      {
          IEnumerable<string> myEnumerable = BlackAndWhite();
          return myEnumerable.GetEnumerator();
      }*
 public IEnumerable<string> BlackAndWhite()//迭代器  {
yield return "black"; yield return "gray"; yield return "white"; } } class Program { static void Main() { var mc = new MyClass(); //该类目前未实现GetEnumerator()使类本身不可枚举,仍然可以使用由迭代器返回的可枚举类,只需要直接调用迭代器方法 foreach (string shade in mc.BlackAndWhite()) { Console.Write(shade); } } }

 

反编译IL代码

C# 迭代器 Iterator

 

上一篇:foreach和IEnumerable+yield和IEnumerator


下一篇:生成器