枚举器和迭代器
枚举器和可枚举类型
枚举器是一个可以依次返回请求的数组中元素的类实例对象,“知道”项的次序并跟踪它在序列中的位置,然后返回请求的当前项。
对于有枚举器的类型而言,必须有一个方法来获取它。获取一个对象枚举器的方法是调用对象的GetEnumerator
方法。实现GetEnumerator
方法的类型叫做可枚举类型
。
class Program
{
public interface IJohn
{
void MyMethod();
}
static void Main()
{
int[] test = { 1, 2, 3 };
var a = test.GetEnumerator();
Type t = a.GetType();
Console.WriteLine($"{t.Name}");
Console.WriteLine($"{typeof(IJohn).Name}");
Console.WriteLine($"{typeof(IEnumerable).Name}");
}
}
IEnumerator接口
实现了IEnumerator
接口的枚举器包含3个函数成员:Current
,MoveNext
以及Reset
。
Current
是返回序列中当前位置项的属性- 它是只读属性
- 返回
object
类型的引用,所以可以返回任何类型
MoveNext
是把枚举器位置前进到集合中下一项的方法,返回布尔值,指示新的位置是有效位置还是已经超过了序列的尾部- 如果新的位置是有效的,方法返回
true
- 如果新的是位置是无效的,方法返回
false
- 枚举器的原始位置在序列中的第一项之前,是-1,因此
MoveNext
必须在第一次使用Current
之前调用。
- 如果新的位置是有效的,方法返回
Reset
是把位置重置为原始状态的方法
static void Main()
{
int[] MyArray = { 10, 11, 12, 13 };
IEnumerator ie = MyArray.GetEnumerator();
while (ie.MoveNext())
{
int i = (int)ie.Current;
Console.WriteLine($"{i}");
}
}
IEnumerable接口
可枚举类是指实现了IEnumerable接口的类。IEnumerable
接口只有一个成员-----GetEnumerator
方法,它返回对象的枚举器。
class MyColors : IEnumerable
{
string[] Colors = { "Red", "Yellow", "Blue" };
public IEnumerator GetEnumerator()
{
return new ColorEnumerator(Colors);
}
}
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()
{
_position = -1;
}
}
class Program
{
public interface IJohn
{
void MyMethod();
}
static void Main()
{
var mc = new MyColors();
foreach (var color in mc)
{
Console.WriteLine(color);
}
}
}
泛型枚举接口
实际上,大多数情况下应该使用泛型版本IEnumeratble<T>
和IEnumerator<T>
,两者的区别如下:
对于非泛型接口形式
:
IEnumerable
接口的GetEnumerator
返回实现IEnumerator
枚举器的实例- 实现
IEnumerator
的类实现了Current
属性,它返回的是object,然后必须把它转化为实际类型的对象
对于泛型接口形式
:
IEnumerable<T>
接口的GetEnumerator
方法返回实现IEnumerator<T>
的枚举器类的实例- 实现
IEnumerator<T>
的类实现了Current
属性,返回的是实际类型的对象,而不是object
基类的引用
由此可见,非泛型接口的实现不是类型安全的,它们返回的是object
类型的引用,然后必须转化为实际类型,而泛型接口的枚举器是类型安全的,它返回的实际类型的引用。
迭代器
迭代器实际是在类中的一种特殊的结构,它返回一个泛型枚举器或可枚举类型,yield return
语句声明这是枚举中的下一项。
迭代器块是由一个或多个yield语句的代码块,它可以是:方法主体;访问器主体;运算符主体;
产生枚举器的迭代器
:
public IEnumerator<string> IteratorMethod()
{
...
yield return ...;
}
产生可枚举类型的迭代器
:
public IEnumerable<string> IteratorMethod()
{
....
yield return ...;
}
使用迭代器来创建枚举器
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class MyClass1
{
public IEnumerator<string> GetEnumerator()
{
return BlackAndWhite();
}
public IEnumerator<string> BlackAndWhite()
{
yield return "Black";
yield return "Gray";
yield return "White";
}
}
class Program
{
static void Main()
{
var mc = new MyClass1();
foreach (var s in mc)
{
Console.WriteLine(s);
}
}
}
}
上述代码,BlackAndWhite
方法是一个迭代器块,可为Myclass1
类产生并返回枚举器,MyClass1
实现了GetEnumerator
方法,这意味着它是可枚举类型,该方法调用了BlackAndWhite
方法,并返回了BlackAndWhite
返回的枚举器。
使用迭代器来创建可枚举类型
使用迭代器来创建可枚举类型意思是迭代器块返回的是可枚举类型,并不是说迭代器块所在的类因为有了迭代器块就变成了可枚举类型,迭代器块所在的类是不是可枚举类型,是由其实现没实现GetEnumerator
方法决定的,如果实现了,则就是可枚举类型,这种情况下,GetEnumerator
方法可调用迭代器产生的可枚举类型的GetEnumerator
方法来返回枚举器。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class MyClass1
{
public IEnumerator<string> GetEnumerator()
{
return BlackAndWhite().GetEnumerator();
}
public IEnumerable<string> BlackAndWhite()
{
yield return "Black";
yield return "Gray";
yield return "White";
}
}
class Program
{
static void Main()
{
var mc = new MyClass1();
foreach (var s in mc)
{
Console.WriteLine(s);
}
}
}
}
常见的迭代器模式
- 在类中设置返回枚举器的迭代器时,就必须通过实现
GetEnumerator
让类可枚举,如果不实现该方法,则设置的枚举器就失去了意义,因为也不能通过调用该迭代器来实现遍历的目的(遍历的是可枚举类,而不是枚举器) - 如果在类中设置返回可枚举类型的迭代器,可以让类实现
GetEnumerator
让类枚举,或者不实现该方法,而让类不可枚举。- 如果实现了
GetEnumerator
,那么该方法必须调用迭代器返回的可枚举类型的GetEnumerator
方法来返回枚举器。 - 如果不实现
GetEnumerator
,仍然可以使用由迭代器返回的可枚举类,只需要调用迭代器方法,即可实现遍历。
- 如果实现了
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class MyClass1
{
//public IEnumerator<string> GetEnumerator()
//{
// return BlackAndWhite().GetEnumerator();
//}
public IEnumerable<string> BlackAndWhite()
{
yield return "Black";
yield return "Gray";
yield return "White";
}
}
class Program
{
static void Main()
{
var mc = new MyClass1();
foreach (var s in mc.BlackAndWhite())
{
Console.WriteLine(s);
}
}
}
}
下面错误实例阐述的是:需要遍历的是可枚举类型,而不是枚举器。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class MyClass1
{
//public IEnumerator<string> GetEnumerator()
//{
// return BlackAndWhite().GetEnumerator();
//}
public IEnumerable<string> BlackAndWhite()
{
yield return "Black";
yield return "Gray";
yield return "White";
}
}
class Program
{
static void Main()
{
var mc = new MyClass1();
foreach (var s in mc.BlackAndWhite().GetEnumerator())
{
Console.WriteLine(s);
}
}
}
}
产生多个可枚举类型
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
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> IRtoUV()
{
for (int i = colors.Length - 1; i >= 0; i--)
{
yield return colors[i];
}
}
}
class Program
{
public static void Main()
{
var a = new Spectrum();
foreach (var color in a.UVtoIR())
{
Console.Write($"{color}->");
}
Console.WriteLine();
foreach (var color in a.IRtoUV())
{
Console.Write($"{color}->");
}
Console.WriteLine();
}
}
}
上述代码,Spectrum
类本身并不是可枚举类型,但它定义了两个枚举类型迭代器,通过调用该迭代器,也可以完成不同的迭代遍历任务。
将迭代器作为属性
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Spectrum
{
bool _listFromUVtoIR;
string[] colors = { "violet", "blue", "cyan", "green", "yellow", "orange", "red" };
public Spectrum(bool listFromUVtoIR)
{
_listFromUVtoIR = listFromUVtoIR;
}
public IEnumerator<string> GetEnumerator()
{
return _listFromUVtoIR
? 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()
{
var startUV = new Spectrum(true);
var startIR = new Spectrum(false);
foreach(string color in startUV)
{
Console.Write($"{color}->");
}
Console.WriteLine();
foreach (string color in startIR)
{
Console.Write($"{color}->");
}
Console.WriteLine();
}
}
}
有关迭代器其他事项
- 迭代器需要
System.Collections.Generic
命名空间 - 在编译生成的枚举器中,
Reset
方法并没有实现,所以调用该方法,会抛出System.NotSupportedException
异常。 - 在后台,由编译器生成的枚举器类是包含4个状态的状态机: