迭代器之謎
一.序言
yiled 關鍵字曾今一直困擾着我,彷彿是夏日清晨的迷霧,原本以爲散去之後從此就不會再有,可是沒想到第二天依舊籠罩在心頭,那麼今天,對,就是此時當下,我要揭開她那神祕的面紗,撥雲見日。
迭代器,這名字很唬人,坦白說初次聽到這個名字我做不到顧名思義,我不知道這是用來幹嘛的,也猜不出一二…
後來,在那個月黑風高的夜晚,只記得,那夜狂風大作,樹葉吱吱作響,只見一個人影,欻欻欻,以迅雷不及掩耳之勢從我眼前掠過,那身姿,輕盈柔美,她是誰,她來自哪,她又將去哪,說時遲那時快,只見我縱身一跳,奪窗而出,可我忘記了,我住的在八樓… 那可是八樓呀…八樓
有時候就是這麼皮,扯遠了,哈哈哈哈…
二.畫龍
先拋開迭代器這個名詞,我先拋出一個問題,假設你需要遍歷一個集合你會怎麼做呢?大多數情況下我們無非就是使用for循環或者foreach。可是又有多少初學的朋友知道之所以能用foreach對一個集合進行遍歷操作,是因爲該集合實現了IEnumerable接口,比如我們熟知的數組Array,列表List,字典Dictionary,這些都實現了IEnumerable接口的,我們可以從這些集合的實現中看到這點,直接上圖看得更明白
那這言外之意說明什麼呢,是不是自己寫一個實現了IEnumerable接口的類,然後就可以用foreach關鍵字來對這個類進行遍歷了。如果你這麼想過,那麼恭喜你,你天生就是爲計算機而生的,骨骼精奇,是編程的一把好手,比我聰明數百倍,我當初就沒這麼想過,哎,我要躲在小角落默默的哭泣了…
好啦,玩笑嬉戲一下,下邊我們來代碼實現一下,然後,你會發現新大陸
代碼的功能是遍歷狼羣中狼的數量
首先,定義狼,內部就一個名字
public class Wolf
{
//名稱
private string name;
public Wolf(string name)
{
this.name = name;
}
public void Log()
{
Debug.Log(name);
}
}
其次,定義狼羣,實現IEnumerable接口,內部定義了一個狼的集合,充當foreach遍歷訪問的數據
public class Wolfs : IEnumerable
{
//狼羣
private Wolf[] wolfs;
public Wolfs()
{
wolfs = new Wolf[2] {
new Wolf("zhangsan"),
new Wolf("lisi")
};
}
//數量
public int count
{
get
{
return wolfs.Length;
}
}
//得到wolfs的元素
//這是索引器的寫法:以數組的形式訪問wolfs
//調用方使用(對象名[0])就可以訪問到wolfs[0]元素
//等同於下邊的Get方法:使用(對象名.Get(0))訪問到wolfs[0]元素
public Wolf this[int i]
{
get
{
return wolfs[i];
}
}
/* public Wolf Get(int i)
{
return wolfs[i];
}*/
//這個方法是IEnumerable中的接口,必須得實現
//返回值是IEnumerator類型的對象
public IEnumerator GetEnumerator()
{
return new WolfsEnumerator(this);
}
}
最後,實現WolfsEnumerator類,不實現可不可以呢?咦,這話問的,GetEnumerator方法返回的就是IEnumerator類型的對象,爲了滿足這一條件就必須實現這麼一個類,最終的目的是使用foreach關鍵字去遍歷狼羣,這是必經之路,這是規定
先看看 IEnumerator接口的原型:可以看到兩個方法和一個屬性,這些都必須在子類中去實現它
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
再來看看實現IEnumerator的子類,目的是爲了遍歷狼羣,這其中的MoveNext()方法是重中之重,因爲foreach一次就是執行一次MoveNext()
public class WolfsEnumerator : IEnumerator
{
//構造中傳入狼羣
public WolfsEnumerator(Wolfs wolfs)
{
this.wolfs = wolfs;
index = 0;
}
//要被遍歷的狼羣
private Wolfs wolfs;
//當前狼羣的索引
private int index;
//遍歷到當前的狼
private Wolf wolf;
//對外的訪問入口
public object Current
{
get
{
return wolf;
}
}
//實現的接口
//
public bool MoveNext()
{
bool result = false;
if (index >= wolfs.count )
{
result = false;
}
else
{
//注意,這裏就是Wolfs裏邊的索引器
//得到Wolfs內部的wolfs集合的對象
//實際上就是給Current對象賦值
wolf = wolfs[index];
index++;
result = true;
}
return result;
}
//實現的接口
public void Reset()
{
index = 0;
}
寫到這裏,在看這篇文章的你肯定還是很迷糊,不要着急,看看是如何被調用的,你必然會有種醍醐灌頂的感覺,來,我們代碼走起
Wolfs wolfs = new Wolfs();
//看到沒,看到沒,有沒有很神奇
foreach (Wolf w in wolfs)
{
w.Log();
}
有沒有什麼神奇,我們自己定義的對象,居然也可以使用foreach進行遍歷,遍歷的是什麼,遍歷的就是Wolfs類中的wolfs集合,所以,我們首尾呼應一下:只要完整實現了IEnumerable接口的對象就可以用foreach關鍵字進行遍歷
我們上邊寫了那麼多,那我們可不可以直接調用實現過的那些方法來實現這一結果呢,我們試試看:
Wolfs wolfs = new Wolfs();
//調用我們實現過的GetEnumerator()方法:返回的是一個IEnumerator對象
IEnumerator tor = wolfs.GetEnumerator();
//通過循環調用我們實現過的MoveNext()方法
while (tor.MoveNext())
{
Wolf w = (Wolf)tor.Current;//通過Current對象進行訪問
w.Log();
}
可以看到:
foreach (Wolf w in wolfs)
{
w.Log();
}
/*------------------二者等價-----------------------*/
/*------------------神不神奇-----------------------*/
IEnumerator tor = wolfs.GetEnumerator();
while (tor.MoveNext())
{
Wolf w = (Wolf)tor.Current;
w.Log();
}
所以,我前邊才說了每執行一次foreach實際上就是執行一次MoveNext()方法。
三.點睛
那,說到這,我還沒有提到到底什麼是迭代器,也沒有提到yield,我是不是跑偏題了,是麼?其實並沒有,我們接着往下看
說曹操曹操就到,yield關鍵字來了,yield關鍵字實際上是爲了簡化代碼而生的,就那上邊的代碼來說,我可以不實現WolfsEnumerator這類,此話一出,我尼瑪是不是直接從你口中脫口而出,你的心裏是不是在想你上邊明明說了這是規定,必須得實現GetEnumerator()方法,而這個方法恰恰返回得就是個IEnumerator類型的對象,你怎麼能說不實現呢?
很好,非常好,你能如此的思考,說明這文章你看的很細緻,就只有那麼一丁點的距離,你就要看到新大陸了,請先抑制住內心的喜悅和驚奇…
這其中的神奇之處就在於yield關鍵字,同樣的,我們上代碼
//首先 定義狼
public class Wolf
{
//名稱
private string name;
public Wolf(string name)
{
this.name = name;
}
public void Log()
{
Debug.Log(name);
}
}
//其次 定義狼羣
//爲了方便閱讀:刪除了不必要的代碼
public class Wolfs : IEnumerable
{
private Wolf[] wolfs;
public Wolfs()
{
wolfs = new Wolf[2] {
new Wolf("zhangsan"),
new Wolf("lisi")
};
}
//這裏是關鍵:省去了WolfsEnumerator類的實現
//WolfsEnumerator類主要實現了:
//狼羣集合數據的傳入,MoveNext()方法,和Current對象的賦值
public IEnumerator GetEnumerator()
{
yield return wolfs[0];
yield return wolfs[1];
/*
for(int i=0;i<wolfs.Length;i++)
{
yield return wolfs[i];
}
*/
}
我只想說神奇不神奇,GetEnumerator()方法中的實現居然用yield return的表達式給替代了,所以說(敲黑板了,敲黑板了),yield return 關鍵字代表着啥呢?其實不代表啥,就是一種語法糖,用於簡化迭代器代碼,迭代器字眼終於出現了,而這裏的WolfsEnumerator就是我們所說的迭代器,它用於使Wolfs對象能夠用foreach關鍵字遍歷其內部的wolfs集合
所以,看到yield return xxx 我們就應該想到IEnumerator接口,想到MoveNext()方法,想到Current屬性,這是其本質,主要是用於輔助一個對象遍歷其中的集合元素
最後,我們可以在來看幾行代碼,來感受下yield return xxx表達式的魅力
IEnumerator Test()
{
//yield return new Wolf("zhangjian");
//yield return new Wolf("lisi");
yield return 1;
yield return 2;
}
void Start()
{
IEnumerator tor = Test();
while(tor.MoveNext())
{
Debug.Log((int)tor.Current);
}
}
可以看到Test()方法背後實際上維護着一個int類型的集合,這個集合當中有兩個元素,分別是1和2,我隱約都看到了隱藏在這個Test()方法背後的TestEnumerator類的實現,當然了,我看到了,不知道你是否也看到了。
到此,迭代器之謎也就落下了帷幕,非常歡迎拍磚,狠狠拍的那種…