|
我們知道通常foreach可以實現(xiàn)對類型的遍歷,但是foreach并不是針對所有類型都可以實現(xiàn)遍歷的功能,那么我們可以思考這樣的一個問題:foreach對類型實施遍歷的依據(jù)條件是什么?它是通過什么方式來實現(xiàn)遍歷的? 下面我們自定義一個類型來嘗試使用foreach進行遍歷,看會發(fā)生什么樣的現(xiàn)象,并且以此作為出發(fā)點來一點點分析foreach的原理。 1.自定義類型并使用foreach遍歷
運行VS后編譯器提示了錯誤,根據(jù)錯誤描述可以推斷出foreach需要調(diào)用Person類型中的”GetEnumerator”方法。
2.在自定義類型中添加”GetEnumerator”方法下一步我們根據(jù)編譯器的要求在Person類型中添加”GetEnumerator”方法,添加該方法后又出現(xiàn)如下的錯誤提示:
根據(jù)編譯器的錯誤提示,可以推斷出Person類的GetEnumerator方法的返回值類型必須要有MoveNext方法和Current屬性,示例中的object類型并沒有MoveNext方法和Current屬性。
3.添加MoveNext方法和Current屬性編譯器的錯誤提示要有MoveNext方法和Current屬性,但是我們無法搞清楚這兩個東西的具體實現(xiàn)形式,比如說MoveNext方法的返回值是什么樣的?Current屬性是只讀還是可讀可寫的? 此時我們可以去已經(jīng)支持foreach的類型中查找GetEnumerator方法返回值類型,并對該類型的實現(xiàn)進行仿寫并用于我們自定義類型當中。string類型作為常用類型并支持foreach遍歷,下面我們查看該string的GetEnumerator方法返回值類型進行仿寫。
仿寫后編譯通過:
以“數(shù)數(shù)”的思想來真正實現(xiàn)foreach遍歷的功能在生活中小朋友們往往通過數(shù)數(shù)的形式來完成對數(shù)學的啟蒙,foreach遍歷的思想就類似于“數(shù)數(shù)”。小朋友數(shù)數(shù)往往需要通過雙手來對事物進行數(shù)數(shù),這就相當于GetEnumerator方法:其作用實際上是需要foreach當前遍歷的類型調(diào)用該方法提供一個用于遍歷當前的類型的“迭代計數(shù)器對象”,GetEnumerator方法的返回值類型:該類型其實就是用于foreach遍歷當前類型的“迭代計數(shù)器對象”,類似于計數(shù)的雙手(工具);Current屬性:就相當于當前數(shù)到的對象(foreach遍歷到的當前元素);MoveNext方法:就相當于“數(shù)數(shù)”中對元素不斷的移動的動作,促使讓“迭代計數(shù)器對象”的計數(shù)移至到下一位。 到目前為止已經(jīng)提供了編譯器錯誤提示的所有成員,但實際運行是毫無意義的,因為通常編程中實現(xiàn)遍歷的對象都是一個序列,但是目前示例中Person類型并不是且成員未包含序列。我們將為示例中Person類型添加一個序列,作為foreach“遍歷的對象”。
實現(xiàn)“數(shù)數(shù)的功能”1.遍歷的數(shù)據(jù)對象添加foreach“遍歷的數(shù)據(jù)對象”后又有問題來了,foreach如何訪問到這個數(shù)據(jù)呢。我們可以將數(shù)據(jù)作為“迭代計數(shù)器對象”構造函數(shù)的參數(shù)在GetEnumerator方法中傳遞過去,然后“迭代計數(shù)器對象”中提供一個字段進行存儲。
2.設置foreach遍歷到的當前元素(Current屬性)目前我們遍歷的“對象”是一個string數(shù)組,所以要讀取這個數(shù)組里的元素,我們需要一個下標索引來讀取遍歷到的當前元素,并作為Current屬性值。下面我們將設置一個int類型值為-1的字段作為“讀取下標”,然后在Current屬性的get方法中通過下標索引器讀取“當前遍歷到的數(shù)據(jù)對象”。
3.MoveNext方法因為我們是通過下標去訪問元素的,所以需要對下標進行遞增進行變化,從而不斷指向下一個元素從而到達累計。MoveNext方法是一個布爾的返回值類型,其主要目的是告知foreach當前遍歷的數(shù)據(jù)對象是否還存在沒有遍歷到的元素,如果存在元素則不斷遞增下標索引并反會true,反之返回false并結(jié)束遍歷,MoveNext方法的返回值相當于foreach遍歷的前提條件。 下面我們通過代碼來實現(xiàn)MoveNext方法:
到目前為止我們已經(jīng)將一個不具備foreach條件的自定義類型,通過自編碼實現(xiàn)了foreach的基本要求。下面我們通過代碼運行看看執(zhí)行結(jié)果:
結(jié)果運行成功,并打印輸出遍歷的數(shù)據(jù)對象(Person中的數(shù)組)。 注意:遍歷該數(shù)組我們并沒有通過foreach直接對其進行的遍歷,而是結(jié)合foreach的流程本質(zhì)和基本要求來實現(xiàn)的遍歷功能。 通過調(diào)試來分析foreach的遍歷的原理
通過調(diào)試結(jié)果我們可以總結(jié)下foreach遍歷主要依靠三個流程:
基于foreach的原理思想,我們還可以將遍歷寫成如下形式:
foreach的寫法其實就是調(diào)用上面的代碼片段,從而實現(xiàn)的一種“語法糖”。 C#中基于原理的實現(xiàn)方式在上文中我們借鑒string類型中的GetEnumerator方法來參照實現(xiàn)“迭代計數(shù)器對象”當中的MoveNext方法和Current屬性。
在.net中看某個類型是否支持使用foreach進行遍歷,其實接可以看該類型和該類型的“迭代計數(shù)器”是否都實現(xiàn)了IEnumerable接口。IEnumerable接口中的成員就包含了foreach實現(xiàn)的原理和需要調(diào)用的成員。 圖一:
圖二:
示例源碼
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 6 Person person = new Person(); 7 foreach (var item in person) 8 { 9 Console.WriteLine(item); 10 } 11 12 var Enumerator = person.GetEnumerator(); 13 while (Enumerator.MoveNext()) 14 { 15 Console.WriteLine(Enumerator.Current); 16 } 17 18 } // END Main() 19 20 }
1 class Person 2 { 3 string[] Datas = new string[] { "張三","李四","王五"}; 4 5 public PersonEnumerator GetEnumerator() 6 { 7 return new PersonEnumerator(Datas); 8 } 9 } 10 11 /// <summary> 12 /// 迭代計數(shù)器 13 /// </summary> 14 class PersonEnumerator 15 { 16 public PersonEnumerator(string[] datas) { this.Datas = datas; } 17 18 /// <summary> 19 /// 遍歷的數(shù)據(jù)對象 20 /// </summary> 21 private string[] Datas; 22 23 private int index = -1; 24 25 /// <summary> 26 /// 當前遍歷到的元素 27 /// </summary> 28 public string Current { 29 get { return Datas[index]; } 30 } 31 32 /// <summary> 33 /// 將記錄指針移至下一條 34 /// </summary> 35 /// <returns>是否存在尚未遍歷的元素</returns> 36 public bool MoveNext() 37 { 38 index++; 39 return index < Datas.Length; 40 } 41 42 } |
|
|