迭代器模式随想

一、什么是迭代器模式?

定义:提供一种顺序访问集合的方法,而不暴露集合内部的表示

顺序访问,one by one(挨个访问),不暴露集合内部表示,反映了面向对象程序中的封装性。可以这么理解,一组模特从后台出场,一个接着一个,但是先出场的模特,未必是站在最前面的模特。换句话说,对于观众,你不知道后台模特的特定位置。为什么是顺序访问呢?因为迭代器模式采用的输出机制是内部决定好的,你无法决定。不像字典类型,我传不同的key,可以访问不同的value。我们访问列表,可以直接访问第i个元素,但是迭代器,你想要访问下一个元素,必须把当前的元素访问过后,才能到下一个元素。

二、c#中的迭代器接口

 

 迭代器接口,可以手动实现调用,如下:

 1     public class MyIEnumerator : IEnumerator<string>
 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<string>
 2     {
 3         public IEnumerator<string> 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<string> GetEnumerator()
2         {
3             yield return "下等马";
4             yield return "上等马";
5             yield return "中等马";
6         }

通过6行代码,编译器就为我们创建好了迭代器,如下所示:

 1 public class MyEnumerable : IEnumerable<string>, IEnumerable
 2 {
 3     // Methods
 4     [IteratorStateMachine(typeof(<GetEnumerator>d__0))]
 5     public IEnumerator<string> 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 <GetEnumerator>d__0 : IEnumerator<string>, 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 <GetEnumerator>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<string>.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<int> ages =new List<int> { 5, 17, 30, 40 };
  var yong = ages.Where(g => g < 18);

  ages.Add(2);

   foreach (var item in yong)
   {
      Console.WriteLine(item);
   }

 

运行结果:

 

Where扩展方法只是生成了一个迭代器,告诉这个迭代器原始的集合,以及如何筛选的方法,到调用的时候,才真正的过滤数据,这就是Linq延迟加载的原理。

 

posted @ 2019-09-04 17:21  micDavid  阅读(388)  评论(0编辑  收藏  举报