.NET教程,這篇文章還是接著上文介紹的第二部分!多的不說,直接獻(xiàn)上內(nèi)容!

使用yield關(guān)鍵字實(shí)現(xiàn)方法GetEnumerator
如果iterator本身有實(shí)現(xiàn)IEnumerator接口(本例就是一個(gè)數(shù)組),則可以有更容易的方法:
public IEnumerator GetEnumerator()
{
return _people.GetEnumerator();
}
注意,這個(gè)方法沒有Foreach的存在,所以如果你改用for循環(huán)去迭代這個(gè)集合,你得自己去呼叫MoveNext,然后獲得集合的下一個(gè)成員。而且會(huì)出現(xiàn)一個(gè)問題,就是你無法知道集合的大小(IEnumerable沒有Count方法,只有IEnumerable才有)。
此時(shí),可以做個(gè)試驗(yàn),如果我們知道一個(gè)集合有3個(gè)成員,故意迭代多幾次,比如迭代10次,那么當(dāng)集合已經(jīng)到達(dá)尾部時(shí),將會(huì)拋出InvalidOperationException異常。
class Program
{
static void Main(string[] args)
{
Person p1 = new Person("1");
Person p2 = new Person("2");
Person p3 = new Person("3");
People p = new People(new Person[3]{p1, p2, p3});
var enumerator = p.GetEnumerator();
//Will throw InvalidOperationException
for (int i = 0; i < 5; i++)
{
enumerator.MoveNext();
if (enumerator.Current != null)
{
var currentP = (Person) enumerator.Current;
Console.WriteLine("current is {0}", currentP.Name);
}
}
Console.ReadKey();
}
}
public class Person
{
public string Name { get; set; }
public Person(string name)
{
Name = name;
}
}
public class People : IEnumerable
{
private readonly Person[] _persons;
public People(Person[] persons)
{
_persons = persons;
}
public IEnumerator GetEnumerator()
{
return _persons.GetEnumerator();
}
}
使用yield關(guān)鍵字配合return,編譯器將會(huì)自動(dòng)實(shí)現(xiàn)繼承IEnumerator接口的類和上面的三個(gè)方法。而且,當(dāng)for循環(huán)遍歷超過集合大小時(shí),不會(huì)拋出異常,Current會(huì)一直停留在集合的最后一個(gè)元素。
public IEnumerator GetEnumerator()
{
foreach (Person p in _people)
yield return p;
}
如果我們在yield的上面加一句:
public IEnumerator GetEnumerator()
{
foreach (var p in _persons)
{
Console.WriteLine("test");
yield return p;
}
}
我們會(huì)發(fā)現(xiàn)test只會(huì)打印三次。后面因?yàn)橐呀?jīng)沒有新的元素了,yield也就不執(zhí)行了,整個(gè)Foreach循環(huán)將什么都不做。
yield的延遲執(zhí)行特性 – 本質(zhì)上是一個(gè)狀態(tài)機(jī)
關(guān)鍵字yield只有當(dāng)真正需要迭代并取到元素時(shí)才會(huì)執(zhí)行。yield是一個(gè)語法糖,它的本質(zhì)是為我們實(shí)現(xiàn)IEnumerator接口。
static void Main(string[] args)
{
IEnumerable items = GetItems();
Console.WriteLine("Begin to iterate the collection.");
var ret = items.ToList();
Console.ReadKey();
}
static IEnumerable GetItems()
{
Console.WriteLine("Begin to invoke GetItems()");
yield return "1";
yield return "2";
yield return "3";
}
在上面的例子中,盡管我們呼叫了GetItems方法,先打印出來的句子卻是主函數(shù)中的句子。這是因?yàn)橹挥性赥oList時(shí),才真正開始進(jìn)行迭代,獲得迭代的成員。我們可以使用ILSpy察看編譯后的程序集的內(nèi)容,并在View -> Option的Decompiler中,關(guān)閉所有的功能對(duì)勾(否則你將仍然只看到一些yield),然后檢查Program類型,我們會(huì)發(fā)現(xiàn)編譯器幫我們實(shí)現(xiàn)的MoveNext函數(shù),實(shí)際上是一個(gè)switch。第一個(gè)yield之前的所有代碼,統(tǒng)統(tǒng)被放在了第一個(gè)case中。
bool IEnumerator.MoveNext()
{
bool result;
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
Console.WriteLine("Begin to invoke GetItems()");
this.<>2__current = "1";
this.<>1__state = 1;
result = true;
return result;
case 1:
this.<>1__state = -1;
this.<>2__current = "2";
this.<>1__state = 2;
result = true;
return result;
case 2:
this.<>1__state = -1;
this.<>2__current = "3";
this.<>1__state = 3;
result = true;
return result;
case 3:
this.<>1__state = -1;
break;
}
result = false;
return result;
}
如果某個(gè)yield之前有其他代碼,它會(huì)自動(dòng)包容到它最近的后續(xù)的yield的“統(tǒng)治范圍”:
static IEnumerable GetItems()
{
Console.WriteLine("Begin to invoke GetItems()");
Console.WriteLine("Begin to invoke GetItems()");
yield return "1";
Console.WriteLine("Begin to invoke GetItems()");
yield return "2";
Console.WriteLine("Begin to invoke GetItems()");
Console.WriteLine("Begin to invoke GetItems()");
Console.WriteLine("Begin to invoke GetItems()");
yield return "3";
}
它的編譯結(jié)果也是可以預(yù)測的:
case 0:
this.<>1__state = -1;
Console.WriteLine("Begin to invoke GetItems()");
Console.WriteLine("Begin to invoke GetItems()");
this.<>2__current = "1";
this.<>1__state = 1;
result = true;
return result;
case 1:
this.<>1__state = -1;
Console.WriteLine("Begin to invoke GetItems()");
this.<>2__current = "2";
this.<>1__state = 2;
result = true;
return result;
case 2:
this.<>1__state = -1;
Console.WriteLine("Begin to invoke GetItems()");
Console.WriteLine("Begin to invoke GetItems()");
Console.WriteLine("Begin to invoke GetItems()");
this.<>2__current = "3";
this.<>1__state = 3;
result = true;
return result;
case 3:
this.<>1__state = -1;
break;
這也就解釋了為什么第一個(gè)打印出來的句子在主函數(shù)中,因?yàn)樗胁皇莥ield的代碼統(tǒng)統(tǒng)都被yield吃掉了,并成為狀態(tài)機(jī)的一部分。而在迭×××始之前,代碼是無法運(yùn)行到switch分支的。
令人矚目的是,編譯器沒有實(shí)現(xiàn)reset方法,這意味著不支持多次迭代:
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
yield只返回,不賦值
下面這個(gè)例子。不過我認(rèn)為Artech大大分析的不是很好,我給出自己的解釋。
class Program
{
static void Main(string[] args)
{
IEnumerable vectors = GetVectors();
//Begin to call GetVectors
foreach (var vector in vectors)
{
vector.X = 4;
vector.Y = 4;
}
//Before this iterate, there are 3 members in vectors, all with X and Y = 4
foreach (var vector in vectors)
{
//But this iterate will change the value of X and Y BACK to 1/2/3
Console.WriteLine(vector);
}
}
static IEnumerable GetVectors()
{
yield return new Vector(1, 1);
yield return new Vector(2, 3);
yield return new Vector(3, 3);
}
}
public class Vector
{
public double X { get; set; }
public double Y { get; set; }
public Vector(double x, double y)
{
this.X = x;
this.Y = y;
}
public override string ToString()
{
return string.Format("X = {0}, Y = {1}", this.X, this.Y);
}
}
我們進(jìn)行調(diào)試,并將斷點(diǎn)設(shè)置在第二次迭代之前,此時(shí),我們發(fā)現(xiàn)vector的值確實(shí)變成4了,但第二次迭代之后,值又回去了,好像被改回來了一樣。但實(shí)際上,并沒有改任何值,yield只是老老實(shí)實(shí)的吐出了新的三個(gè)vector而已。Yield就像一個(gè)血汗工廠,不停的制造新值,不會(huì)修改任何值。
從編譯后的代碼我們發(fā)現(xiàn),只要我們通過foreach迭代一個(gè)IEnumerable,我們就會(huì)跑到GetVectors方法中,而每次運(yùn)行GetVectors方法,yield都只會(huì)返回全新的三個(gè)值為(1,1),(2,2)和(3,3)的vector,仿佛第一次迭代完全沒有運(yùn)行過一樣。原文中,也有實(shí)驗(yàn)證明了vector創(chuàng)建了六次,實(shí)際上每次迭代都會(huì)創(chuàng)建三個(gè)新的vector。
解決這個(gè)問題的方法是將IEnumerable轉(zhuǎn)為其子類型例如List或數(shù)組。
在迭代的過程中改變集合的狀態(tài)
foreach迭代時(shí)不能直接更改集合成員的值,但如果集合成員是類或者結(jié)構(gòu),則可以更改其屬性或字段的值。不能在為集合刪除或者增加成員,這會(huì)出現(xiàn)運(yùn)行時(shí)異常。For循環(huán)則可以。
var vectors = GetVectors().ToList();
foreach (var vector in vectors)
{
if (vector.X == 1)
//Error
//vectors.Remove(vector);
//This is OK
vector.X = 99;
Console.WriteLine(vector);
}
IEnumerable的缺點(diǎn)
IEnumerable功能有限,不能插入和刪除。
訪問IEnumerable只能通過迭代,不能使用索引器。迭代顯然是非線程安全的,每次IEnumerable都會(huì)生成新的IEnumerator,從而形成多個(gè)互相不影響的迭代過程。
在迭代時(shí),只能前進(jìn)不能后退。新的迭代不會(huì)記得之前迭代后值的任何變化。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
當(dāng)前標(biāo)題:.NET教程:.NET面試題之IEnumerable(二)-創(chuàng)新互聯(lián)
URL標(biāo)題:http://chinadenli.net/article6/coheig.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)網(wǎng)站建設(shè)、電子商務(wù)、虛擬主機(jī)、網(wǎng)頁設(shè)計(jì)公司、定制網(wǎng)站、網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容