一起来阅读《C#图解教程》吧 枚举器与迭代器

枚举器与可枚举类型

以数组为例说明,数组之所以可以使用foreach进行遍历,本质上是因为它是一个可枚举类型,什么是可枚举类型?
可枚举类型可以按需提供一个叫做枚举器的对象,枚举器可以一次返回请求的数组中的元素,枚举器知道项的次序并且跟踪它在序列中的位置,然后返回请求的当前项。
获取一个对象枚举器的方法是调用对象的GetEnumerator方法,实现GetEnumerator方法的类型叫做可枚举类型(enumerable)。

IEnumerator接口

IEnumerator接口是枚举器的要实现的接口,包含3个函数成员:Current,MoveNext以及Reset

  • Current是返回序列中当前位置项的属性
  • MoveNext是把枚举器位置前进到集合中下一项的方法。它也返回布尔值,指示新的位置是有效位置还是已经超过了序列的尾部。
    • 如果新的位置是有效的,方法返回true
    • 如果新的位置是无效的(比如当前位置到达了尾部),方法返回false
    • 枚举器的原始位置在序列中的第一项之前,因此№ve№Xt必须在第一次使用(耵rent之前调用。
  • Reset是把位置重置为原始状态的方法。

枚举器与序列中的当前项保持联系的方式完全取决于实现。可以通过对象引用、索引值或其他方式来实现对于内置的一维数组来说,就使用项的索引。

IEnumerable 接口

可枚举类是指实现了IEnumerable接口的类。IEnumerable接口只有一个成员 — GetEnumerator 方法,它返回对象的枚举器。
image.png
一个完整的例子:

    class Program
    {
        static void Main(string[] args)
        {

            Spectrum spectrum = new Spectrum();
            foreach (var color in spectrum)
            {
                Console.WriteLine(color);
            }

            while (true)
            {
                
            }
        }
    }


//枚举器
    class ColorEnumerator : IEnumerator
    {
        private string[] _colors;
        private int _position = -1;

        public ColorEnumerator(string[] theColors)
        {
            _colors = new String [theColors.Length];
            for (int i = 0; i < theColors.Length; i++)
            {
                _colors[i] = theColors[i];
            }
        }

        public bool MoveNext()
        {
            if (_position < _colors.Length -1)
            {
                _position++;
                return true;
            }

            return false;
        }

        public void Reset()
        {
            _position = -1;
        }

        public object Current
        {
            get
            {
                if (_position == -1)
                {
                    throw new InvalidOperationException();
                }

                if (_position >= _colors.Length)
                {
                    throw new InvalidOperationException();
                }

                return _colors[_position];
            }
        }
    }

//可枚举类型
    class Spectrum:IEnumerable
    {
        private string[] Colors = {"red", "blue", "black", "orange"};

        public IEnumerator GetEnumerator()
        {
            return new ColorEnumerator(Colors);
        }
    }

泛型枚举接口

很多情况下,我们需要使用泛型版本的枚举接口IEnumerable和IEnumerator.
对于非泛型来说,他返回的是object的引用,然后我们必须把它转化成实际类型的对象。
对于泛型来说,他范湖id是实际类型的对象,而不是object基类的引用。

迭代器

C#从2.0版本开始提供了更简单的创建枚举器和可枚举类型的方式。实际上,编译器将为我们创建它们。这种结构叫做迭代器(iterator)。我们可以把手动编码的可枚举类型和枚举器替换为由迭代器生成的可枚举类型和枚举器。

public IEnumerator<string> BlackAndWhite()
{
  yield return "black";
  yield return "gray";
  yield return "white";
}

上面的代码为一个 “迭代器”,它是一个产生和返回枚举器的迭代器

  • 迭代器返回一个泛型枚举器,该枚举器返回3个string类型的项。
  • yield return 语句声明这是枚举中的下一项。

迭代器块

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

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

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

  • yield return 语句指定了序列中返回的下一项
  • yield break 语句指定了在序列中没有其他项,类似普通函数里的return.

image.png

使用迭代器创建枚举器

以下演示如何使用迭代器创建可枚举类型

    class Program
    {
        static void Main(string[] args)
        {
            MyClass mc = new MyClass();

            foreach (var shade in mc)
            {
                Console.WriteLine(shade);
            }


            while (true)
            {
                
            }
        }
    }


    public class MyClass
    {
        public IEnumerator<string> GetEnumerator()
        {
            return BlackAndWhite();
        }

        public IEnumerator<string> BlackAndWhite()
        {
            yield return "black";
            yield return "gray";
            yield return "white";
        }
    }
  • BlackAndWhite方法是一个迭代器块,可以为MyClass类产生返回枚举器的方法。
  • 注意Main方法,由于MyC1ass类实现了GetIEnumerator,是可枚举类型,我们在foreach语句中直接使用了类的实例

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

以下演示用迭代器来黄建可枚举类型,而非枚举器。

    class Program
    {
        static void Main(string[] args)
        {

            MyClass2 mc2 = new MyClass2();

            foreach (var shade in mc2)
            {
                Console.WriteLine(shade);
            }

            foreach (var shade in mc2.BlackAndWhite())
            {
                Console.WriteLine(shade);
            }

            while (true)
            {
                
            }
        }
    }
    public class MyClass2
    {
        public IEnumerator<string> GetEnumerator()
        {
            IEnumerable<string> myEnumerable = BlackAndWhite();//获取可枚举类型
            return myEnumerable.GetEnumerator();//获取枚举器
        }

        public IEnumerable<string> BlackAndWhite()//返回可枚举类型
        {
            yield return "black";
            yield return "gray";
            yield return "white";
        }
    }
  • 本例中,BlackAndWhite迭代器方法返回IEnumerator,MyClass类通过返回由BlackAndWhite返回的对象来实现GetEnumerator方法。
  • BlackAndWhite迭代器方法返回IEnumerable而不是IEnumerator因此,MyClass首先调用BlackAndWhite方法获取它的可枚举类型对象,然后调用对象的GetEnumerator方法来获取它的结果,从而实现GetEnumerator方法
  • 在Main的foreach语句中,我们可以使用类的实例,也可以直接调用BlackAndWhite方法,因为它返回的是可枚举类型

常见的迭代器模式

当我们实现返回枚举器的迭代器时,必须通过实现GetEnumerator来让类可枚举,它返回由迭代器返回的枚举器。如下图左边所示
如果我们在类中实现迭代器返回可枚举类型,我们可以让类实现GetEnumerator来让类本身可被枚举,或不实现GetEnumerator,让类不可枚举

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

image.png

迭代器实质

关于迭代器:

  • 迭代器需要system. collections.Generic让命名空间,因此我们需要使用using指令引人它
  • 在编译器生成的枚举器中,Reset方法没有实现。而它是接口需要的方法,因此调用时总是抛出System.NotsupportedException异常。注意,在下图中Reset方法显示为灰色。在后台,由编译器生成的枚举器类是包含4个状态的状态机。
  • Before首次调用MoveNext的初始状态
  • Running调用MoveNext后进人这个状态。在这个状态中,枚举器检测并设置下一项的位置。在遇到yield return、yield break或在迭代器体结束时,退出状态。
  • Suspended状态机等待下次调用MoveNext的状态。
  • After 没有更多项可以枚举

如果状态机在Before或Suspended状态时调用了MoveNext方法,就转到了Running状态。在 Running状态中,它检测集合的下一项并设置位置。如果有更多项,状态机会转人Suspended状态,如果没有更多项,它转人并保持在After状态。
image.png

posted @   LemonInCup  阅读(89)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示