迭代器模式随想
一、什么是迭代器模式?
定义:提供一种顺序访问集合的方法,而不暴露集合内部的表示
顺序访问,one by one(挨个访问),不暴露集合内部表示,反映了面向对象程序中的封装性。可以这么理解,一组模特从后台出场,一个接着一个,但是先出场的模特,未必是站在最前面的模特。换句话说,对于观众,你不知道后台模特的特定位置。为什么是顺序访问呢?因为迭代器模式采用的输出机制是内部决定好的,你无法决定。不像字典类型,我传不同的key,可以访问不同的value。我们访问列表,可以直接访问第i个元素,但是迭代器,你想要访问下一个元素,必须把当前的元素访问过后,才能到下一个元素。
二、c#中的迭代器接口
迭代器接口,可以手动实现调用,如下:
复制代码
1 public class MyIEnumerator : IEnumerator
2 {
3 string[] types = { "下等马", "上等马", "中等马" };
4
5 int cur = -1;
6 public string Current
7 {
8 get
9 {
10 return types[cur];
11 }
12 }
13 object IEnumerator.Current
14 {
15 get
16 {
17 return this.Current;
18 }
19 }
20
21 public void Dispose()
22 {
23 }
24
25 public bool MoveNext()
26 {
27 if (cur < types.Length - 1)
28 {
29 cur++;
30 return true;
31 }
32 return false;
33 }
34
35 public void Reset()
36 {
37 cur = -1;
38 }
39 }
复制代码
话说田忌赛马,按一定的出场顺序赢得了齐威王,此策略是由孙膑提出的,再看看调用:
复制代码
1 MyIEnumerator m = new MyIEnumerator();
2 while (true)
3 {
4 if (m.MoveNext()) { Console.WriteLine(m.Current); }
5 else break;
6 }
复制代码
显然手动编写代码,比较麻烦,搞不好还弄个数组越界,我们看看c#中可枚举接口:
此接口只有一个实现迭代器的方法,我们知道凡是实现了这个接口的,都可以用foreach循环,我们把调用迭代器的方法改成foreach自动调用
复制代码
1 public class MyEnumerable : IEnumerable
2 {
3 public IEnumerator GetEnumerator()
4 {
5 return new MyIEnumerator();
6 }
7
8 IEnumerator IEnumerable.GetEnumerator()
9 {
10 return GetEnumerator();
11 }
12 }
复制代码
复制代码
1 MyEnumerable a = new MyEnumerable();
2 foreach (var item in a)
3 {
4 Console.WriteLine(item);
5 }
复制代码
可能有人问了,你改成foreach循环和手动循环调用有什么区别?可以这么说吧,foreach进一步简化了调用,你不用控制循环什么时候结束,你也不要操心怎么访问下一个元素。
可见迭代器的调用已经很优雅了,如果迭代器的创建能够简化,那么就更好了,c#中提供了yield关键字。
复制代码
1 public IEnumerator GetEnumerator()
2 {
3 yield return "下等马";
4 yield return "上等马";
5 yield return "中等马";
6 }
复制代码
通过6行代码,编译器就为我们创建好了迭代器,如下所示:
复制代码
1 public class MyEnumerable : IEnumerable, IEnumerable
2 {
3 // Methods
4 [IteratorStateMachine(typeof(d__0))]
5 public IEnumerator GetEnumerator()
6 {
7 yield return "下等马";
8 yield return "上等马";
9 yield return "中等马";
10 }
11
12 IEnumerator IEnumerable.GetEnumerator()
13 {
14 return this.GetEnumerator();
15 }
16
17 // Nested Types
18 [CompilerGenerated]
19 private sealed class d__0 : IEnumerator, IDisposable, IEnumerator
20 {
21 // Fields
22 private int <>1__state;
23 private string <>2__current;
24 public MyEnumerable <>4__this;
25
26 // Methods
27 [DebuggerHidden]
28 public d__0(int <>1__state)
29 {
30 this.<>1__state = <>1__state;
31 }
32
33 private bool MoveNext()
34 {
35 switch (this.<>1__state)
36 {
37 case 0:
38 this.<>1__state = -1;
39 this.<>2__current = "下等马";
40 this.<>1__state = 1;
41 return true;
42
43 case 1:
44 this.<>1__state = -1;
45 this.<>2__current = "上等马";
46 this.<>1__state = 2;
47 return true;
48
49 case 2:
50 this.<>1__state = -1;
51 this.<>2__current = "中等马";
52 this.<>1__state = 3;
53 return true;
54
55 case 3:
56 this.<>1__state = -1;
57 return false;
58 }
59 return false;
60 }
61
62 [DebuggerHidden]
63 void IEnumerator.Reset()
64 {
65 throw new NotSupportedException();
66 }
67
68 [DebuggerHidden]
69 void IDisposable.Dispose()
70 {
71 }
72
73 // Properties
74 string IEnumerator.Current
75 {
76 [DebuggerHidden]
77 get
78 {
79 return this.<>2__current;
80 }
81 }
82
83 object IEnumerator.Current
84 {
85 [DebuggerHidden]
86 get
87 {
88 return this.<>2__current;
89 }
90 }
91 }
92 }
复制代码
有趣的是编译器用switch case 实现MoveNext方法,yield用状态机实现迭代器。
三、理解yield关键字
yield关键字,后面紧跟return 表达式或者break。return 表达式,并没有结束迭代器,只是暂时离开迭代器,在特定情况下(调用MoveNext方法时),又会进入迭代器。可以理解迭代器处于暂停或者挂起状态。break直接结束迭代。
四、迭代器的其它用途
Linq表达式的延迟加载,如果我们有一个数组,想通过Linq过滤一下:
复制代码
List ages =new List { 5, 17, 30, 40 };
var yong = ages.Where(g => g < 18);
ages.Add(2);
foreach (var item in yong)
{
Console.WriteLine(item);
}
复制代码
运行结果:
Where扩展方法只是生成了一个迭代器,告诉这个迭代器原始的集合,以及如何筛选的方法,到调用的时候,才真正的过滤数据,这就是Linq延迟加载的原理。https://www.cnblogs.com/wangqiang3311/p/11460373.html