C#学习笔记 —— 枚举器和迭代器

枚举器和迭代器

1、枚举器和可枚举类型

(1)使用foreach语句

  • 数组可以按需提供一个枚举器(enumerator)的对象.

  • 枚举器可以一次返回请求中的数组

  • 枚举器知道项的次序并且跟踪他在序列中的位置

  • 对于有枚举器的类型而言, 必须有一种方法能够获取他

    • 获取对象枚举器的方法是调用对象的GetEnumerator方法, 实现GetEnumerator的类型叫做可枚举类型

    • 数组是可枚举类型

  • foreach结构设计用来和可枚举类型一起使用, 只要给他的遍历对象是可枚举类型, 比如数组, 他就会执行如下行为

    • 通过调用GetEnumerator方法来获取对象的枚举器

    • 从枚举其中请求每一项并且把它作为迭代变量, 代码可读取该变量但是不能改变

foreach(Type VarName in 可枚举对象)
{
  ...
}

 

2、IEnumerator接口

实现了Ienumerator接口的枚举类包含三个函数成员

  • Current

    • 返回序列中当前位置项的属性

    • 他是只读属性

    • 返回object类型的引用, 所以可以返回任何类型的对象

  • MoveNext

    • 把枚举器位置前进到集合下一项的方法

    • 返回布尔值, 指示新的位置是有效位置, 还是已经超过了序列的尾部

    • 如果新的位置是有效的返回真, 否则代表当前位置到达了尾部返回假

    • 枚举器的原始位置在序列的第一项之前, 因此MoveNext必须在第一次使用Current之前调用

  • Reset 重置为原始状态

  • 枚举器跟踪序列中当前项的方式完全取决于实现, 可以通过对象引用、索引值或者其他方式来实现

  • 对于内置的一维数组来说, 就使用项的索引

3、IEnumerable接口

  • 可枚举类实现了IEnumerable接口的类

  • IEnumerable接口只有一个成员,GetEnumerator方法,它发挥对象的枚举器

using System.Collections;
class MyClass : IEnumerable
{
   public IEnumerator GetEnumerator {}
}

使用IEnumerable、IEnumerator的示例

class ColorEnumerator : IEnumerator
{
   string[] colors;
   int position = -1;
   public ColorEnumerator(string[] theColors)
  {
       colors = new string[theColors.Length];
       for(int i = 0; i < theColors.Length; i++)
      {
           colors[i] = theColors[i];
      }
  }
   //实现IEnumrator Current
   public object Current
  {
       get
      {
           if(position < 0 || position >= colors.Length)
          {
               throw new InvalidOperationException();
          }
           return colors[position];
      }
  }
   //实现MoveNext
   public bool MoveNext()
  {
       if(position < colors.Length - 1)
      {
           position++;
           return true;
      }
       else
      {
           return false;
      }
  }
   //实现重置
   public void Reset()
  {
       position = -1;
  }
}
class Spectrum : IEnumerable
{
   string[] Colors = {"blue", "yellow", "green", "black"};
   public IEnumerator GetEnumerator()
  {
       return new ColorEnumerator(Colors);
  }
}
static void Main(string[] args)
{
    Spectrum spectrum = new Spectrum();
     foreach(string color in spectrum)
    {
         Console.WriteLine(color);
    }
}

4、泛型枚举接口

  • 对于非泛型接口形式

    • IEnumerable接口的`GetEnumerator方法返回实现IEnumerator的枚举器类实例

    • 实现IEnumerator的类实现了Current属性, 他返回object类型的引用,然后我们必须把他转换为对象的实际类型

  • 泛型接口继承自非泛型接口, 对于泛型接口

    • IEumerable<T>接口的GetEnumerator方法返回实现IEnumator<T>的枚举器的实例

    • Current属性,返回实际类型的实例, 而不是返回object类型的引用

    • 这些是协变接口,所以他们的实际声明就是IEumerable<out T>, IEumerator<out T>

5、迭代器 using System.Collections.Generic;

  • 迭代器是C#编译器为我们提供的更简单的创建枚举器和可枚举类型的方式

  • yield return声明这是枚举的下一项

(1)迭代器块

  • 迭代器块是有应该或多个yield语句的代码块 下面三种代码块中任意一种都可以

    • 方法主体

    • 访问器主体

    • 运算符主体

  • 迭代器块与其他代码块不同

    • 其他块包含的语句被当作是命令式的,先执行代码块的第一个语句,然后执行后面的语句,最后离开控制块

    • 迭代器块不是需要在同一时间执行的一串命令式命令,而是声明性的描述了希望编译器为我们创建的枚举器的行为,迭代器块中的代码描述了如何枚举元素

  • 迭代器块有两个特殊语句

    • yield return语句指定了序列中要返回的下一项

    • yield break语句指定在序列中没有其他项

  • 编译器得到有关如何枚举项的描述后,使用它来构建包含所有需要的方法和属性实现的枚举器类,产生的类被嵌套包含在声明迭代器的类中

//生产枚举器
public IEnumerator<string> IteratorMethod()
{
   yield return...
}
//生成可枚举类型
public IEnumerable<string> IteratorMethod()
{
   yield return...
}

(2)使用迭代器来创建枚举器

  • BlackAndWhite是迭代器块,可以为MyClass类产生返回枚举器的方法

  • MyClass实现GetEnumerator(),并且调用迭代器块,并且返回枚举器,

  • MyClass是可枚举类型,所以可以直接在主方法中使用foreach,它不检查接口,只检查接口的实现

class MyClass
{
   //实现GetEnumerator(),是可枚举类型
   public IEnumerator<string> GetEnumerator()
  {
       return BlackAndWhite();
  }
   //用迭代器产生枚举器
   public IEnumerator<string> BlackAndWhite()
  {
       yield return "black";
       yield return "gray";
       yield return "white";
  }
}
static void Main(string[] args)
{
   MyClass mc = new MyClass();
   foreach (string color in mc)
  {
     Console.WriteLine(color);
  }
}

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

  • BlackAndWhite迭代器方法返回IEnumerable<string>可枚举对象

  • 因此MyClass首先调用BlackAndWhite()获取它的可枚举对象,然后调用对象的GetEnumerator()来获取它的结果,从而实现GetEnumerator()

  • 在主方法中,可以使用类的实例,也可以直接调用BlackAndWhite()因为他返回的是可枚举类型

class MyClass
{
   public IEnumerator<string> GetEnumerator()
  {
       //获取可枚举类型
       IEnumerable<string> myEnumerable = BlackAndWhite();
       //获取枚举器
       return myEnumerable.GetEnumerator();
  }
   //用迭代器产生可枚举类型
   public IEnumerable<string> BlackAndWhite()
  {
       yield return "black";
       yield return "gray";
       yield return "white";
  }
}
static void Main(string[] args)
{
   MyClass mc = new MyClass();
   //使用类对象
   foreach (string color in mc)
  {
     Console.WriteLine($"{color}");
  }
   //使用类枚举器方法
    foreach (string color in mc.BlackAndWhite())
  {
     Console.WriteLine($"{color}");
  }
}

6、常见迭代器模式

(1)枚举器迭代器模式

  • 必须通过实现GetEnumerator()来让类实现可枚举,返回由迭代器返回的枚举器

class MyClass
{
   public IEnumerator<XXX> GetEnumerator()
  {
       return IteratorMethod();
  }
   public IEnumerator<XXX> IteratorMethod()
  {
       yield return xxx;
  }
}
static void Main(string[] args)
{
   MyClass mc = new MyClass();
   //使用类对象
   foreach (XXX xxx in mc)
  {
     Console.WriteLine($"{xxx}");
  }
}

(2)可枚举类型的迭代器模式

  • 可以让类实现GetEnumerator()来让类本身可枚举,或不实现GetEnumerator()让类不可枚举

    • 如果实现,让他调用迭代器方法一获取自动生成的实现IEnumerable的类实例,然后从IEnumerable对象返回由GetIEnumerable()创建的枚举器

    • 如果不实现,类本身不可枚举,仍然可以使用由迭代器返回的可枚举类,只需要直接调用迭代器方法,也就是使用类枚举器方法

class MyClass
{
   public IEnumerable<XXX> GetEnumerator()
  {
       return IteratorMethod().GetEnumerator();
  }
   public IEnumerable<XXX> IteratorMethod()
  {
       yield return xxx;
  }
}
static void Main(string[] args)
{
   MyClass mc = new MyClass();
   //使用类对象
   foreach (XXX xxx in mc)
  {
     Console.WriteLine($"{xxx}");
  }
   //使用类枚举器方法
    foreach (XXX xxx in mc.IteratorMethod())
  {
     Console.WriteLine($"{xxx}");
  }
}

7、产生多个可枚举类型

  • 下列类有两个可枚举类型的迭代器, 从UV -> IR 和 IR -> UV

  • 尽管有两个方法返回可枚举类型,类本身不是可枚举类型,没实现GetEnumerator()

class Spectrum
{
   string[] colors = {"blue", "green", "yellow"};
   //产生可枚举类型
   public IEnumerable<string> UVtoIR()
  {
       for(int i = 0; i < colors.Length; i++)
      {
           yield return colors[i];
      }
  }
   //产生可枚举类型
   public IEnumerable<string> IRtoUV()
  {
       for(int i = colors.Length - 1 ; i >= 0 ; i--)
      {
           yield return colors[i];
      }
  }
}
static void Main(string[] args)
{
   Spectrum sp = new Spectrum();
   //使用类枚举器方法
   foreach (string color in sp.UVtoIR())
  {
     Console.WriteLine($"{color}");
  }
   //使用类枚举器方法
    foreach (string color in sp.IRtoUV())
  {
     Console.WriteLine($"{color}");
  }
}

8、将迭代器作为属性

  • 使用迭代器来产生具有两个枚举器的类

  • 演示迭代器如何能实现为属性而不是方法

class Spectrum
{
   //根据这个属性来返回枚举器
   bool _listFromUVtoIR;
   
   string[] colors = {"blue", "green", "yellow"};
   
   //构造器
   public Spectrum(bool listFromUVtoIR)
  {
       _listFromUVtoIR = listFromUVtoIR;
  }
   
   //产生枚举器
   public IEnumerator<string> GetEnumerator()
  {
       return _listFromUVtoIR ? UVtoIR : IRtoUV;
  }
   
   public IEnumerator<string> UVtoIR()
  {
       for(int i = 0; i < colors.Length; i++)
      {
           yield return colors[i];
      }
  }
   public IEnumerator<string> IRtoUV()
  {
       for(int i = colors.Length - 1 ; i >= 0 ; i--)
      {
           yield return colors[i];
      }
  }
}
static void Main(string[] args)
{
   Spectrum spUV = new Spectrum(true);
   Spectrum spIR = new Spectrum(false);
   //使用类枚举器
   foreach (string color in spUV)
  {
     Console.WriteLine($"{color}");
  }
   //使用类枚举器
    foreach (string color in spUV)
  {
     Console.WriteLine($"{color}");
  }
}

9、迭代器的实质

  • 迭代器需要using System.Collections.Generic;命名空间

  • 在编译器生成的枚举器,不支持Reset他是接口需要的方法,所以实现它会导致调用时抛出异常

  • 在后台,由编译器生成的枚举器类是包含4个状态的状态机

    • Before 首次调用MoveNext之前的初始状态

    • Running 调用MoveNext后进入这个状态

      • 枚举器检测并设置下一项的位置,在遇到yield return yield break或在迭代器体结束,退出状态

    • Suspended 等待下次调用MoveNext的状态

    • After 没有更多项可以枚举的状态

  • 如果状态机在BeforeSuspended状态时调用了MoveNext方法,就转到了Running状态,在Running状态中,它检测集合的下一项并设置位置

  • 如果由更多项会转入Suspended,如果没有,他转入并保持在After状态

 

posted on   老菜农  阅读(56)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律

导航

统计信息

点击右上角即可分享
微信分享提示