迭代器模式隨想

  • 2019 年 10 月 3 日
  • 筆記

一、什麼是迭代器模式?

定義:提供一種順序訪問集合的方法,而不暴露集合內部的表示

順序訪問,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延遲載入的原理。