(九)枚举器和迭代器(2)

一、Enumerator 接口

实现了 IEnumerator 接口的枚举器包含3个函数成员:Current、MoveNext 以及 Reset。

1)Current: 返回序列中当前位置项的属性。

  • 只读属性。
  • 返回 Object 类型。可以返回对应的可枚举类型。

2)MoveNext: 枚举器位置移到集合中下一个元素的方法,返回值为 bool,返回值代表位置的状态,是有效位置还是到末尾项的位置。

  • 返回值为 true,说明新的位置是有效的。
  • 返回值为 false,说明新的位置无效,也有可能到了末尾的位置。
  • 枚举器的原始位置在序列中的第一项之前,因此 MoveNext 必须在第一次使用 Current 之前调用。

3)Reset: 把位置重置为原始状态的方法。

(九)枚举器和迭代器(2)_第1张图片

二、IEnumberable 接口

可枚举类型之所以能通过 GetEnumerator 方法来获取枚举器对象,是因为 IEnumberable 接口里有这个方法,并且可枚举类型实现了 IEnumberable 接口的类。

class MyClass:IEnumerable
{
public IEnumberator GetEnumerator{...}
}

(九)枚举器和迭代器(2)_第2张图片

使用 IEnumerbalbe 和 IEnumerator 的示例

    //枚举器
    class ColorEnumberator : IEnumerator
    {
        string[] colors;
        int position = -1;

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

        public object Current
        {
            get
            {
                if(position == -1)
                throw new InvalidOperationException();
                if(position >= colors.Length)
                    throw new InvalidOperationException();

                return colors[position];
            }
        }

        public bool MoveNext()
        {
            if(position < colors.Length - 1)
            {
                position++;
                return true;
            }
            else
            {
                return false;
            }
        }

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

    //可枚举类型
    class  Spectrum:IEnumerable
    {
        string[] Colors = { "violet", "blue", "cyan", "green", "yellow", "orange", "red" };

        public IEnumerator GetEnumerator()
        {
            //创建自定义枚举器ColorEnumberator 对象
            return new ColorEnumberator(Colors);//此处断点调试
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Spectrum spectrum = new Spectrum();
            foreach (string color in spectrum)//此处断点调试,可进入到GetEnumerator() 方法里
                Console.WriteLine(color);
            Console.ReadKey();
        }
    }

IEnumerable: 用来实现可枚举类型对象,并调用 GetEnumerator 方法获取枚举器
IEnumerator: 用来自定一个枚举器类型

三、泛型枚举接口

泛型枚举接口:

IEnumerable
IEnumerator

对于非泛型接口:

  • IEnumerable 接口的 GetEnumerator 方法返回实现 IEnumerator 的枚举器实例;
  • 实现 IEnumerator 的类实现了 Current 属性,它返回 object 类型的引用,然后我们必须把它转化为对象的实际类型。

泛型接口继承自非泛型接口。对于泛型接口形式:

  • IEnumerable 接口的GetEnumerator 方法返回实现 IEnumertor 接口的枚举器的实例。
  • 实现 IEnumertor 的类实现了 Current 属性,它返回实际类型的实例,而不是object 基类的引用。
  • 这些是协变接口,所以它们实际声明为 IEnumerable < out T> 、 IEnumertor < out T>

类型安全
==非泛型接口的实现不是类型安全的。==它们返回 object 类型的引用,然后必须转化为实际类型。
泛型接口的枚举器是类型安全的,它返回实际类型的引用。
图19-7实现 IEnumerator 接口的类的结构

(九)枚举器和迭代器(2)_第3张图片

图19-7实现 IEnumerable 接口的类的结构

(九)枚举器和迭代器(2)_第4张图片

四、迭代器

编译器为我们创建了枚举器和可枚举类型,这种结构叫作迭代器

1、声明迭代器

以下是声明两个迭代器的版本

版本1

//声明迭代器 BlackAndWhite;
public IEumerator BlackAndWhite()
{
yield return "black";//如果没有返回,则执行下一条返回语句
yield return "gray";
yield return "white";
}

版本2

//声明迭代器 BlackAndWhite;
public IEumerator BlackAndWhite()
{
string[] theColors = { "black","gray","white" };
for(int i = 0;i < theColors.Length;i++)
yield return theColors[i];
//如果没有返回则再遍历一次;如果能返回,则结束后续迭代
}

yield return 在枚举器中的作用: 每遍历一次访问 Current 属性时,就要返回一个新值,而不是一次返回所有元素。

2、迭代器块

迭代器块是有一个或多个 yield 语句的代码块。

迭代器块有3种:

  • 方法主体;
  • 访问器主体;
  • 运算器主体。

迭代器块与其他代码块不同。其他块包含的语句被当作是命令式的。也就是说,先执行代码块的第一个语句,然后执行后面的语句,最后控制离开块。

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

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

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

可以让迭代器产生枚举器或可枚举类型

//产生枚举器的迭代器
public IEnumerator IteratorMethod()
{
yield retrun...
}

//产生可枚举类型的迭代器
public IEnumerable IteratorMethod()
{
yield retrun...
}

使用迭代器来创建枚举器

 class  MyClass
    {
        public int  CurrentIndex = 0;
        //由于实现了GetEnumerator方法,MyClass 类是可枚举类型
        public IEnumerator GetEnumerator()
        {
            return BlackAndWhite();//返回枚举器
        }

        public IEnumerator BlackAndWhite()//迭代器
        {
            yield return "black ";
            yield return "gray ";
            yield return "white " ;
            //返回枚举器 IEnumerator
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyClass mc = new MyClass();
            foreach (string shade in mc)
                Console.WriteLine(shade);

            Console.ReadKey();
        }
    }

输出结果:

black
gray
white

问题: BlackAndWhite 的作用是通过迭代器来创建一个枚举器,可 foreach 语句为什么会遍历输出 BlackAndWhite 方法里的所有返回值呢?

要想回答这个问题,以下我就通过把 BlackAndWhite 方法调整为:
(在 MyClass 类添加了一个 变量 CurrentIndex,以追踪枚举器获取当前值的位置)

  public IEnumerator BlackAndWhite()//迭代器
        {
        //第一次遍历,返回含有black内容的枚举器
            yield return "black " + CurrentIndex;
            CurrentIndex++;
            
            //第二次遍历,返回含有gray内容的枚举器
            yield return "gray " + CurrentIndex;
            CurrentIndex++;
            
          //第三次遍历,返回含有white内容的枚举器
            yield return "white " + CurrentIndex;

            //该注释掉的代码块,其输出结果跟以上不含有CurrentIndex的代码相同
            //迭代器里含有字符串数组,是可枚举类型中的内容
            //string[] theColors = { "black", "gray", "white" };
            //for (int i = 0; i < theColors.Length; i++)
            //    yield return theColors[i];
        }

输出结果:

black 0
gray 1
white 2

由以上的代码可知,foreach 每次遍历访问元素,首先都要调用一次 BlackAndWhite 方法,然后再返回其枚举器,执行 当前 yield return 之后,不会继续执行下行代码,直到下一次遍历的时候,才会继续下一条 yield return 语句。

(九)枚举器和迭代器(2)_第5张图片

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

  class  MyClass
    {
        public IEnumerator GetEnumerator()
        {
            IEnumerable mEnumerable = BlackAndWhite();//获取可枚举类型
            return mEnumerable.GetEnumerator();//获取枚举器
        }

        //IEnumerable:返回可枚举类型
        public IEnumerable BlackAndWhite()
        {
            yield return "black";
            yield return "gray";
            yield return "white";

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

            foreach (string shade in mc)//使用类对象
                Console.Write($"{ shade }  ");

            foreach (string shade in mc.BlackAndWhite())//使用枚举器方法
                Console.Write($"{ shade }  ");

            Console.ReadKey();
        }
    }

输出结果:

black gray white black gray white

(九)枚举器和迭代器(2)_第6张图片

3、总结常见迭代器模式

枚举器的迭代器模式

class MyClass
{
public IEnumerator GetEnumerator()
{
return IteratorMethod();
}

public IEnumerator IteratorMethod()
{
...
yield return ...;
}
}

void Main()
{
MyClass mc = new MyClass();
foreach(string x in mc)
...
}

可枚举类型的迭代器模式

class MyClass
{
public Ienumerator GetEnumerator()
{
return IteratorMethod().GetEnumerator();
}

public IEnumerable IteratorMethod()
{
yield return...
}

void Main()
{
MyClass mc = new MyClass();
foreach(var string x in mc)
...

foreach(string x in mc.IteratorMethod())
...

}
}

4、产生多个可枚举类型

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

Specturm 类有两个可枚举类型的迭代器 —— 一个从紫外线到红外线枚举光谱中的颜色,而另一个以逆序进行枚举。Spectrum 类不是可枚举类型,但是它有两个方法返回可枚举类型。

    class Spectrum
    {
        string[] colors = { "violet", "blue", "cyan", "green", "yellow", "orange", "red" };
         //返回可枚举类型
        public IEnumerable UVtoIR()
        {
            for (int i = 0; i < colors.Length; i++)
                yield return colors[i];
        }
         //返回可枚举类型
        public IEnumerable IRtoUV()
        {
            for (int i = colors.Length - 1; i >= 0; i--)
                yield return colors[i];
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Spectrum spectrum = new Spectrum();

            foreach (string color in spectrum.UVtoIR())
                Console.Write($"{ color }   ");
            Console.WriteLine();

            foreach (string color in spectrum.IRtoUV())
                Console.Write($"{ color }   ");
            Console.WriteLine();

            Console.ReadKey();
        }
    }

输出结果:

violet blue cyan green yellow orange red
red orange yellow green cyan blue violet

5、将迭代器作为属性

本例演示两个方面的内容:

  • 1)使用迭代器来产生具有两个枚举器的累;
  • 2)演示迭代器如何实现为属性而不是方法。
   class Spectrum
    {
        bool _listFromUVtoIR;
        string[] colors = { "violet", "blue", "cyan", "green", "yellow", "orange", "red" };

        public Spectrum(bool lisatFromUVtoIR)
        {
            _listFromUVtoIR = lisatFromUVtoIR;
        }

        public IEnumerator GetEnumerator()
        {
            return _listFromUVtoIR ? UVtoIR : IRtoUV;
        }

        public IEnumerator UVtoIR
        {
            get
            {
                for (int i = 0; i < colors.Length; i++)
                    yield return colors[i];
            }
        }

        public IEnumerator IRtoUV
        {
            get
            {
                for (int i = colors.Length - 1; i >= 0; i--)
                    yield return colors[i];
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Spectrum startUV = new Spectrum(true);
            Spectrum startIR = new Spectrum(false);

            foreach (string color in startUV)
                Console.Write($"{ color }   ");
            Console.WriteLine();

            foreach (string color in startIR)
                Console.Write($"{ color }   ");
            Console.WriteLine();

            Console.ReadKey();
        }
    }

输出结果:

violet blue cyan green yellow orange red
red orange yellow green cyan blue violet

6、迭代器的实质

迭代器的其他重要事项

  • 迭代器属于 System.Collection.Generic 命名空间里的需要使用 using 指令引入它。
  • 在编译器生成的枚举器中,不支持 Reset 方法。它是接口需要的方法,所以实现了它,但调用时总是抛出 System.NotSupportedException 异常。

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

  • Before: 首次调用 MoveNext 之前的初始化状态。
  • Running: 调用 MoveNext 后进入这个状态。在这个状态中,枚举器检测并设置下一项的位置。在遇到 yield return、yield break 或在迭代器体结束时,退出状态。
  • Suspended: 状态机等待下次调用 MoveNext 的状态。
  • After: 没有更多项可以枚举的状态。

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

如果有更多项,状态机会转入 Suspended 状态;如果没有更多项,它转入并保持在 After 状态。

(九)枚举器和迭代器(2)_第7张图片

你可能感兴趣的:(CSharp,c#,开发语言)