詳解 C# 的 foreach 工作原理
原創(chuàng)【51CTO.com原創(chuàng)稿件】作為開發(fā)人員我們經(jīng)常會在程序中編寫 foreach 語句實現(xiàn)對類型的遍歷,但是并不是所有的類型都可以遍歷,這個知識點是絕大部分開發(fā)成員所知曉的。但是類型可以被 foreach 遍歷的依據(jù)是什么部分程序員并不清楚,下面我就通過舉例的方式來具體講解 foreach 原理。
在這里我們首先自定義一個類型 Cat 并遍歷這個類型:
- //定義 Cat 類型
- class Cat
- {
- }
- //遍歷 Cat
- class Program
- {
- static void Main(string[] args)
- {
- Cat cat = new Cat();
- foreach(var item in cat)
- {
- //more code
- }
- }
- }
我們運行上述代碼后編譯器會提示錯誤 “Cat” 不包含 “GetEnumerator” 的公共定義,因此 foreach 語句不能作用于 “Cat” 類型的變量,由此錯誤提示我們可以得知如果 Cat 類型可以被 foreach 遍歷,那么 Cat 類就必須實現(xiàn) GetEnumerator 方法。下面我們就在 Cat 類中加入 GetEnumerator 方法。
- class Cat
- {
- //加入 GetEnumerator 方法的實現(xiàn)
- public object GetEnumerator()
- {
- return null;
- }
- }
我們再次運行代碼,這時程序出現(xiàn)如下兩個錯誤提示:
-
foreach 要求 “Cat.GetEnumerator()”的返回類型 “object”必須具有適當(dāng)?shù)墓?MoveNext 方法和公共 Current 屬性;
-
object 并不包含 “MoveNext” 的定義。
根據(jù)上述錯誤提示我們可以推斷出 GetEnumerator 方法的返回值必須要有 MoveNext 方法和 Current 屬性。但是我們目前并不知道 GetEnumerator 方法的返回值類型和 Current 屬性是否是只讀的,這種情況我們該怎么辦呢?此時我們可以查看已經(jīng)支持 foreach 遍歷的類型是怎么做的,下面的代碼段展示了 string 類型是如何實現(xiàn)的(只列出了關(guān)鍵代碼)。
- //more code
- public CharEnumerator GetEnumerator();
- //more code
- pubic sealed class CharEnumerator:ICloneabe,IEnumerator<char>,IEnumerator,IDisposable
- {
- public char Current {get;}
- //more code
- public bool MoveNext();
- //more code
- }
根據(jù)上述代碼段我們仿寫如下:
- class Cat
- {
- public CatEnumerator GetEnumerator()
- {
- return new CatEnumerator();
- }
- }
- class CatEnumerator
- {
- public char Current {get;}
- public bool MoveNext()
- {
- return true;
- }
- }
這時我們編譯發(fā)現(xiàn)原來的錯誤已經(jīng)消失了,程序編譯通過了。但是不要以為到這里就完了,Cat 類僅僅包含這些是沒有任何意義的,這些內(nèi)容只是為了讓程序通過編譯而已,在實際開發(fā)中我們遍歷的對象是一個序列,那么我們現(xiàn)在就在 Cat 類中添加一個固定的序列:
- class Cat
- {
- string[] datas=new string[]{"波斯貓","貍花貓","無毛貓","虎斑貓"};
- public CatEnumerator GetEnumerator()
- {
- return new CatEnumerator();
- }
- }
我們已經(jīng)添加了數(shù)據(jù)對象,那么 foreach 是如何訪問到這個數(shù)據(jù)的呢?這時我們可以將數(shù)據(jù)對象通過 GetEnumerator 方法作為迭代計數(shù)器對象(CatEnumerator)構(gòu)造函數(shù)的參數(shù)傳遞進去,然后迭代計數(shù)器對象提供一個屬性將這些數(shù)據(jù)存儲起來。
- class Cat
- {
- string[] datas=new string[]{"波斯貓","貍花貓","無毛貓","虎斑貓"};
- public CatEnumerator GetEnumerator()
- {
- return new CatEnumerator(datas);
- }
- }
- class CatEnumerator
- {
- //存儲數(shù)據(jù)
- private string[] datas;
- //帶參構(gòu)造函數(shù)
- public CatEnumerator(string[] datas)
- {
- this.datas=datas;
- }
- public char Current {get;}
- public bool MoveNext()
- {
- return true;
- }
- }
到目前為止我們已經(jīng)設(shè)置了遍歷的數(shù)據(jù),如果要將數(shù)據(jù)遍歷出來還需要一個下標索引來讀取數(shù)組中的每個元素,并將每次讀取出來的元素值賦值給 Current 屬性。我們可以在迭代計數(shù)器對象中定義一個 index 整型私有屬性作為下標索引屬性,這里需要注意的是我們 index 這個屬性的默認值為 -1 ,這一點是很多新手開發(fā)人員比較容易出錯的地方。既然有下標了,我們在遍歷的時候下標就必須是遞增變化,不斷指向下一個元素的位置直到到達數(shù)組的末端為止。這時我們就需要在 MoveNext 方法中進行執(zhí)行下標遞增的操作了,MoveNext 方法是一個返回值為 bool 類型的方法,其目的是告知 foreach 當(dāng)前遍歷的數(shù)據(jù)對象是否存在還未遍歷到的元素,如果存在就返回 true 反之返回 false 遍歷結(jié)束。下面我們針對這一段所說的內(nèi)容進行代碼編寫。
- class CatEnumerator
- {
- //存儲數(shù)據(jù)
- private string[] datas;
- //帶參構(gòu)造函數(shù)
- public CatEnumerator(string[] datas)
- {
- this.datas=datas;
- }
- //數(shù)組下標
- private int index=-1;
- //遍歷當(dāng)前元素
- public char Current
- {
- get
- {
- return datas[index];
- }
- }
- public bool MoveNext()
- {
- index++;
- return index < datas.Length;
- return true;
- }
- }
到目前為止我們就編寫了一個可以通過 foreach 遍歷的類型,這里有三點很重要:
-
GetEnumerator 方法的作用是 foreach 調(diào)用當(dāng)前需要遍歷的類型的迭代計數(shù)器對象,該方法的返回類型為用于foreach 遍歷的迭代計數(shù)器對象;
-
Current 屬性就是當(dāng)前遍歷到的對象;
-
MoveNext 方法促使迭代計數(shù)器對象的計數(shù)移動到下一位。
通過前面所述的內(nèi)容,我們可知 foreach 遍歷主要有三個步驟:
-
foreach 調(diào)用當(dāng)前可遍歷類型的 GetEnumerator 方法創(chuàng)建一個迭代計數(shù)器對象,并將要遍歷的數(shù)據(jù)傳遞給迭代計數(shù)器對象的構(gòu)造函數(shù)中;
-
迭代計數(shù)器對象調(diào)用它 MoveNext 方法將所以小標遞增 1 ,若下標大于數(shù)據(jù)長度則迭代完成;
-
MoveNext 方法返回 true 并返回 Current 屬性中存儲的數(shù)據(jù)。
以上三個步驟總結(jié)起來就是 獲取迭代計數(shù)器對象 >> 調(diào)用 MoveNext 方法 >> 獲取 Current 屬性。
作者簡介
朱鋼,筆名喵叔,國內(nèi)某技術(shù)博客認證專家,.NET高級開發(fā)工程師,7年一線開發(fā)經(jīng)驗,參與過電子政務(wù)系統(tǒng)和AI客服系統(tǒng)的開發(fā),以及互聯(lián)網(wǎng)招聘網(wǎng)站的架構(gòu)設(shè)計,目前就職于一家初創(chuàng)公司,從事企業(yè)級安全監(jiān)控系統(tǒng)的開發(fā)。
【51CTO原創(chuàng)稿件,合作站點轉(zhuǎn)載請注明原文作者和出處為51CTO.com】