文章不定时更新,有问题的话欢迎各位dalao指正。
枚举器和迭代器
枚举器与可枚举类型
foreach:
我们可以通过foreach来便历或取出数组中的每一个元素:
int[] arr1 = { 10, 11, 12, 13 };
foreach(int item in arr1)
{
Console.WriteLine($"value:{item}");
}
输出:
value:10
value:11
value:12
value:13
foreach中in后面跟的参数必须是可枚举类型(enumerable type)。
可枚举类型是实现了GetEnumerator方法的类型,而GetEnumerator方法是获取对象枚举器的方法,对于枚举器而言,必须有个方法去获取他。
IEnumerator和IEnumerable
1、IEnumerator
namespace System.Collections
{
//
// 摘要:
// Supports a simple iteration over a non-generic collection.
public interface IEnumerator
{
//
// 摘要:
// Gets the element in the collection at the current position of the enumerator.
//
// 返回结果:
// The element in the collection at the current position of the enumerator.
object? Current { get; }
//
// 摘要:
// Advances the enumerator to the next element of the collection.
//
// 返回结果:
// true if the enumerator was successfully advanced to the next element; false if
// the enumerator has passed the end of the collection.
//
// 异常:
// T:System.InvalidOperationException:
// The collection was modified after the enumerator was created.
bool MoveNext();
//
// 摘要:
// Sets the enumerator to its initial position, which is before the first element
// in the collection.
//
// 异常:
// T:System.InvalidOperationException:
// The collection was modified after the enumerator was created.
void Reset();
}
}
Current、MoveNext()、Reset是枚举器的三个成员
Current只读,获取集合中枚举器当前位置的元素
MoveNext()将枚举器前进到集合的下一个元素。
Reset()将枚举数设置为其初始位置,即集合中第一个元素之前的位置(-1)。
其实在我们使用foreach语句的时候,C#编译器会生成和下面非常相似的代码。
int[] arr1 = { 10, 11, 12, 13 };
IEnumerator ie = arr1.GetEnumerator();
while (ie.MoveNext())//移到下一项
{
int i = (int)ie.Current;//获取当前项
Console.WriteLine($"{i}");
}
10
11
12
13
2、IEnumerable
实现了IEnumerable接口的类叫做可枚举类。
IEnumerable接口中的GetEnumerator 方法返回对象的枚举器。
声明:
class MyClass : IEnumerable//实现IEnumerable接口
{
public IEnumerator GetEnumerator() {
//函数返回IEnumerator类型的对象
}
}
使用IEnumerator和IEnumerable的示例:
class ColorEnumerator : IEnumerator//枚举器
{
string[] _colors;
int _position = -1;//设置初始位置在第一个元素之前即为-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
{//获取当前位置元素
get
{
if (_position == -1)
throw new InvalidOperationException();
if( _position>=_colors.Length)
throw new InvalidOperationException();
return _colors[_position];
}
}
public bool MoveNext()
{//如果可以移动的话向下一个元素位置移动,移动成功返回true,失败返回false
if (_position < _colors.Length - 1)
{
_position++;
return true;
}
else
{
return false;
}
}
public void Reset()
{
_position = -1;
}
}
class Spectrum : IEnumerable//可枚举类Spectrum,他的枚举器是ColorEnumerator
{
string[] Colors = { "yellow", "red", "blue", "pink", "purple", "green", "orange" };
public IEnumerator GetEnumerator()
{
return new ColorEnumerator(Colors);
}
}
class Program
{
static void Main(string[] args)
{
Spectrum spectrum = new Spectrum();
foreach(string color in spectrum)
{
Console.WriteLine(color);
}
}
}
输出:
yellow
red
blue
pink
purple
green
orange
泛型枚举接口
IEnumerable< T>和IEnumerator< T>
注意,非泛型接口的实现不是类型安全的,他们返回object类型的引用,然后必须转化为实际类型。
二者中:非泛型接口中IEnumerator的Current属性返回的是object的引用,而IEnumerator< T>的Current属性返回的是实际类型的对象。
IEnumerable< T>接口的GetEnumerator方法返回实现IEnumator< T>的枚举器类的实例。
迭代器:
可枚举类和枚举器我们已经了解了,下面我们介绍迭代器,这是一种创建二者更简单的方式。
迭代器返回一个泛型枚举器,其中yield return声明这是枚举中的下一项。
第一种实现方式:
public IEnumerator<string> BlackAndWhite()
{
yield return "black";
yield return "and";
yield return "white";
}
第二种:
public IEnumerator<string> BlackAndWhite1()
{
string[] theColors = { "black", "and", "white" };
for(int i = 0; i < theColors.Length; i++)
{
yield return theColors[i];
}
}
如果方法在第一个yield return中返回,那么之后的语句就永远不会执行。而且枚举器不会一次返回所有元素,每次访问Current属性时返回一个新值。
迭代器块
迭代器块是有一个或多个yield语句的代码块。
迭代器块中,yield return指定序列中返回的下一项,yield break指定在序列中没有其他项。
使用迭代器创建枚举器
使用迭代器创建可枚举类,看代码:
class MyClass
{
public IEnumerator<string> GetEnumerator()
{
//返回枚举器
return BlackAndWhite();
}
//BlackAndWhite方法是一个迭代器块,可以为MyClass类产生返回枚举器的方法
//
public IEnumerator<string> BlackAndWhite()//迭代器
{
yield return "black";
yield return "and";
yield return "white";
}
}
class Class1
{
static void Main()
{
MyClass mc = new MyClass();
//mc是枚举器类MyClass的实例
foreach(string shade in mc)
{
Console.WriteLine(shade);
}
}
}
输出
black
and
white
创建的类中包含两部分:产生返回枚举器方法的迭代器、返回枚举器的GetEnumerator。
BlackAndWhite迭代器方法返回IEnumerator< string>,MyClass类通过返回由BlackAndWhite返回的对象来实现GetEnumerator方法。
使用迭代器创建可枚举类型
使用迭代器创建可枚举类型,先看代码:
class MyClass
{
public IEnumerator<string> GetEnumerator()
{
IEnumerable<string> myEnumerable = BlackAndWhite();//获取可枚举类型
//获取枚举器
return myEnumerable.GetEnumerator();
}
//返回可枚举类型
public IEnumerable<string> BlackAndWhite()//迭代器
{
yield return "black";
yield return "and";
yield return "white";
}
}
class Class1
{
static void Main()
{
MyClass mc = new MyClass();
//mc是枚举器类MyClass的实例
foreach (string shade in mc)
{
Console.WriteLine(shade);
}
Console.WriteLine();
//mc.BlackAndWhite是类枚举器方法
foreach (string shade in mc.BlackAndWhite())
{
Console.WriteLine(shade);
}
}
}
输出:
black
and
white
black
and
white
在Main中我们可以直接使用类的实例,也可以直接调用BlackAndWhite方法,因为它返回的是可枚举类型。
常见迭代器模式
常见的有两种:
1、枚举器的迭代器模式
2、可枚举类型的迭代器模式
其实就是上面例子中的使用迭代器创建枚举器和使用迭代器创建可枚举类型
产生多个可枚举类型
下面的代码中specrum类中有两个可枚举类型的迭代器,一个正序输出数组中的颜色,另一个逆序。
注意:虽然两个方法返回可枚举类型,但是类本身不是可枚举类型,因为它没有实现GetEnumerator
class Specrum
{
string[] colors = { "violet", "blue", "yellow", "pink", "black", "orange" };
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 Class2
{
static void Main()
{
Specrum sp = new Specrum();
foreach(string color in sp.UVtoIR())
{
Console.WriteLine(color);
}
Console.WriteLine();
foreach (string color in sp.IRtoUV())
{
Console.WriteLine(color);
}
}
}
输出:
violet
blue
yellow
pink
black
orange
orange
black
pink
yellow
blue
violet
将迭代器作为属性
代码描述
1、使用迭代器来产生具有两个枚举器的类
2、迭代器如何能实现为属性而不是方法
class Special
{
bool _listFromUVtoIR;
string [] colors = { "violet", "blue", "yellow", "pink", "black", "orange" };
//构造方法
public Special(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 Class3
{
static void Main()
{
Special s1 = new Special(true);
Special s2 = new Special(false);
foreach(string color in s1)
{
Console.WriteLine(color);
}
Console.WriteLine();
foreach(string color in s2)
{
Console.WriteLine(color);
}
}
}
输出:
violet
blue
yellow
pink
black
orange
orange
black
pink
yellow
blue
violet
上面的代码声明两个属性来定义两个不同的枚举器,GetEnumerator方法根据_listFromUVtoIR布尔值返回两个枚举器中的一个,当传入true的时候返回UVtoIR枚举器,反之传入IRtoUV枚举器
综上
手动实现迭代器是很麻烦的,我们可以借助yield语句简化迭代。
在实际开发中也有使用迭代器的场景:从时间段中迭代日期、迭代读取文件中的每一行、使用迭代块和迭代条件来对集合进行进行惰性过滤(这一点LINQ也可以做到,在后面的文章中会介绍LINQ)。
C#对很多的设计模式都有间接实现,我们要学会这些设计模式才能更好地运用C#语言带给我们的便捷。