CSharp(C#)语言_高级篇(枚举器和迭代器)【要认真哟o(* ̄▽ ̄*)ブ】

枚举器和可枚举类型

  声明一个有6个元素的数组,我们可以使用foreach来循环打印出数组中的各项的值
  那么为什么数组可以这样做?原因是数组可以按需提供一个叫做 枚举器 的对象
  枚举器可以一次返回请求的数组中的元素。枚举器 “知道” 项的次序并且跟踪他在序列中的位置然后返回请求的当前项
  对于枚举器的类型而言,必须有一个方法来获取它。获取一个对象枚举器当方法是调用对象的GitEnumerator方法。实现 GitEnumerator 方法的类型叫做 可枚举类型 数组是可枚举类型

下图描述了 可枚举类型和枚举器之间的关系
枚举器和可枚举类型概览
  foreach结构设计用来和可枚举类型一起使用。只要给它的遍历对象是可枚举类型,比如数组,它就会执行如下行为:

  • 通过调用GetEnumerator方法获取对象的枚举器
  • 从枚举器中请求每一项并且把它作为迭代变量,代码可以读取该变量但不可以改变
//                        必须是可枚举类型
foreach( Type VarNase in EnumerableObject )
{
    
    
	...
}

IEnumerator接口

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

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

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

IEnumerable接口

  可枚举类是指实现了IEnumerable接口的类。IEnumerable接口只有一个成员——GetEnumerator方法,它返回对象的枚举器

枚举类型的声明形式

using System.Collections;

          // 实现IEnumerable接口
class MyClass :IEnumerable
{
    
    
	// 返回IEnumerator类型的对象
	public IEnumerator GetEnumerator {
    
     ... }
		...
} 

模拟 ColorIEnumerator 的实现

using System.Collections;

class MyColors:IEnumerable
{
    
    
	string[] Colors = {
    
     "Red", "Yellow", "Blue" };
	public IEnumerator GetEnumerator()
	{
    
    
	 			// 枚举器类的实例
		return new ColorEnumerator(Colors);
	}
}

使用 IEnumerable 和 IEnumerator 的示例

using System;
using System.Collections;

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];
	}
	
	public object Current // 实现Current
	{
    
    
		get
		{
    
    
			if (_position == -1 )
				throw new InvalidOperationException();
			if (_position >= _colors.Length )
				throw new InvalidOperationException();
			return _colors[_position];
		}
	}
	
	public bool MoveNext() // 实现MoveNext
	{
    
    
		if ( _position < _colors.Length - 1 )
		{
    
    
			_position ++;
			return true;
		}
		else
			return false;
	}
	
	public void Reset() // 实现Reset
	{
    
    
		_position = -1;
	}
}

class Spectrum : IEnumerable
{
    
    
	string[] Colors = {
    
     "violet", "blue", "cyan", "green","yellow", "orange", "red" };
	
	public IEnumerator CetEnumerator()
	{
    
    
		return new ColorEnumerator( Colors );
	}
}

class Program
{
    
    
	static void Main()
	{
    
    
		Spectrum spectrum = new Spectrum();
		
		foreach( string color in spectrum )
			Console.Writeline( color );
	}
}

泛型枚举接口

枚举接口的非泛型和泛型的本质差别

  • 对于非泛型接口形式:
    • IEnumerable接口的GetEnumerator方法返回实现IEnumerator枚举器类的实例
    • 实现IEnumerator的类实现了Current属性,它返回object的引用,然后我们必须把它转化为实际类型的对象
  • 对于泛型接口形式:
    • IEnumerable<T>接口的GetEnumerator方法返回实现IEnumator<T>的枚举器类的实例
    • 实现IEnumerator<T>的类实现了Current属性,它返回实际类型的对象,而不是object基类的引用

泛型枚举接口的实现

迭代器

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

迭代器块

3中迭代器块:
  方法主体
  访问器主体
  运算符主体

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

常见迭代器模式

  • 当我们实现返回枚举器的迭代器时,必须通过实现GetEnumerator来让类可枚举,它返回由迭代器返回的枚举器
  • 如果我们在类中实现迭代器返回可枚举类型,我们可以让类实现GetEnumerator来让类本身可被枚举,或不实现GetEnumerator,让类不可枚举
    • 如果实现CetEnumerator,让它调用迭代器方法以获取自动生成的实现IEnumerable的类实例。然后,从IEnumerable对象返回由GetEnumerator创建的枚举器
    • 如果通过不实现GetEnumerator使类本身不可枚举,仍然可以使用由迭代器返回的可枚举类,只需要直接调用迭代器方法

产生多个可枚举类型

using System;
using System.Collections.Generic;

class Spectrum
{
    
    
	string[] colors ={
    
     "violet", "blue", "cyan”, “green", "yellow", "orange", "red" };

       					// 返回一个可枚举类型
	public IEnumerable<string> UVtoIR()
	{
    
    
		for ( int i = 0; i < colors.Length; i++)
			yield return colors[i]);
	}
						// 返回一个可枚举类型
	public IEnumerable<string> IRtoU()
	{
    
    
		for ( int i = colors.Length -1; i >= 0; i--)
			yleld return colors[i];
	}
}

class Program
{
    
    
	static void Main()
	{
    
    
		Spectrum spectrum = new Spectrum();

		foreach ( string color in spectrum.UVtoIR())
			Console.Write("{0}",color );
		Console.WriteLine();
		
		foreach ( string color in spectrum.IRtoUV())
			Console.Write("{O}",color );
		Console.Writeline();
	}
}

将迭代器作为属性

using System;
using System.Collections.Generic;

class Spectrum
{
    
    
	bool _listFromUVtoIR;
	string[] colors = {
    
     "violet", "blue", "cyan", "green","yellow", "orange", "red");

	public Spectrum( bool listFromVtoIR )
	{
    
    
		_listFromlVtoIR = listFromUVtoIR;
	}
	
	public IEnumerator<string> GetEnumerator()
	{
    
    
		return _listFromlVtoIR ? UVtoIR : IRtoUV;
	}
	
	public IEnumerator<string> UVtoIR
	{
    
    
		get
		{
    
    
			for ( int i = 0; i < colors.Length; i++ )
				yield return colors[i];
		}
	}

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

class Program
{
    
    
	static void Main()
	{
    
    
		Spectrum startUV = new Spectrum( true );
		Spectrum startIR = new Spectrum( false );
		
		foreach ( string color in startuV )
			Console.Write("{0}", color );
		Console.Writeline();
		
		foreach ( string color in startIR )
			Console.Write("{0}", color );
		Console.WriteLine();
	}
}

迭代器的实质

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

迭代器状态机
迭代器状态机

猜你喜欢

转载自blog.csdn.net/qq_43562262/article/details/107321538