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 2023-07-06 01:12  老菜农  阅读(37)  评论(0编辑  收藏  举报

导航