C#中的yield關(guān)鍵字詳解
在"C#中,什么時(shí)候用yield return"中,我們了解到:使用yield return返回集合,不是一次性加載到內(nèi)存中,而是客戶端每調(diào)用一次就返回一個(gè)集合元素,是一種"按需供給"。本篇來重溫yield return的用法,探秘yield背后的故事并自定義一個(gè)能達(dá)到y(tǒng)ield return相同效果的類,最后體驗(yàn)yield break的用法。
回顧yield return的用法
以下代碼創(chuàng)建一個(gè)集合并遍歷集合。
class Program
{
static Random r = new Random();
static IEnumerable<int> GetList(int count)
{
List<int> list = new List<int>();
for (int i = 0; i < count; i++)
{
list.Add(r.Next(10));
}
return list;
}
static void Main(string[] args)
{
foreach(int item in GetList(5))
Console.WriteLine(item);
Console.ReadKey();
}
}使用yield return也能獲得同樣的結(jié)果。修改GetList方法為:
static IEnumerable<int> GetList(int count)
{
for (int i = 0; i < count; i++)
{
yield return r.Next(10);
}
}通過斷點(diǎn)調(diào)試發(fā)現(xiàn):客戶端每顯示一個(gè)集合中的元素,都會(huì)到GetList方法去獲取集合元素。
探密yield
使用yield return獲取集合,并遍歷。
class Program
{
public static Random r = new Random();
static IEnumerable<int> GetList(int count)
{
for (int i = 0; i < count; i++)
{
yield return r.Next(10);
}
}
static void Main(string[] args)
{
foreach(int item in GetList(5))
Console.WriteLine(item);
Console.ReadKey();
}
}生成項(xiàng)目,并用Reflector反編譯可執(zhí)行文件。在.NET 1.0版本下查看GetList方法,發(fā)現(xiàn)該方法返回的是一個(gè)GetList類的實(shí)例。原來yield return是"語(yǔ)法糖",其本質(zhì)是生成了一個(gè)GetList的實(shí)例。

那GetList實(shí)例是什么呢?點(diǎn)擊Reflector中<GetList>鏈接查看。

- 原來GetList類實(shí)現(xiàn)了IEnumerable和IEnumerator的泛型、非泛型接口
- yield return返回的集合之所以能被迭代、遍歷,是因?yàn)镚etList內(nèi)部有迭代器
- yield return之所以能實(shí)現(xiàn)"按需供給",是因?yàn)镚etList內(nèi)部有一個(gè)_state字段記錄這上次的狀態(tài)
接下來,就模擬GetList,我們自定義一個(gè)GetRandomNumbersClass類,使之能達(dá)到y(tǒng)ield return相同的效果。
using System;
using System.Collections;
using System.Collections.Generic;
namespace ConsoleApplication2
{
class Program
{
public static Random r = new Random();
static IEnumerable<int> GetList(int count)
{
GetRandomNumbersClass ret = new GetRandomNumbersClass();
ret.count = count;
return ret;
}
static void Main(string[] args)
{
foreach(int item in GetList(5))
Console.WriteLine(item);
Console.ReadKey();
}
}
class GetRandomNumbersClass : IEnumerable<int>, IEnumerator<int>
{
public int count;//集合元素的數(shù)量
public int i; //當(dāng)前指針
private int current;//存儲(chǔ)當(dāng)前值
private int state;//保存遍歷的狀態(tài)
#region 實(shí)現(xiàn)IEnumerator接口
public int Current
{
get { return current; }
}
public bool MoveNext()
{
switch (state)
{
case 0: //即為初始默認(rèn)值
i = 0;//把指針調(diào)向0
goto case 1;
break;
case 1:
state = 1;//先設(shè)置原狀態(tài)
if (!(i < count))//如果指針大于等于當(dāng)前集合元素?cái)?shù)量
{
return false;
}
current = Program.r.Next(10);
state = 2; //再設(shè)置當(dāng)前狀態(tài)
return true;
break;
case 2: //再次遍歷如果state值為2
i++;//指針再移動(dòng)一位
goto case 1;
break;
}
return false;
}
//被顯式調(diào)用的屬性
object IEnumerator.Current
{
get { return Current; }
}
public void Reset()
{
throw new NotImplementedException();
}
public void Dispose()
{
}
#endregion
#region 實(shí)現(xiàn)IEnumerable的泛型和非泛型
public IEnumerator<int> GetEnumerator()
{
return this;
}
//被顯式調(diào)用的屬性
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
}關(guān)于GetRandomNumbersClass類:
- count表示集合的長(zhǎng)度,可以在客戶端賦值。當(dāng)調(diào)用迭代器的MoveNext方法,需要把count和當(dāng)前位置比較,以決定是否可以再向前移動(dòng)。
- 字段i相當(dāng)于索引,指針每次移動(dòng)一位,i需要自增1
- current表示當(dāng)前存儲(chǔ)的值,外部通過IEnumerator.Current屬性訪問
迭代器的MoveNext方法是關(guān)鍵:
- state字段是整型,表示產(chǎn)生集合過程中的3種狀態(tài)
- 當(dāng)state為0的時(shí)候,說明是初始狀態(tài),把索引位置調(diào)到0,并跳轉(zhuǎn)到state為1的部分
- 當(dāng)state為1的時(shí)候,首先把狀態(tài)設(shè)置為1,然后判斷索引的位置有沒有大于或等于集合的長(zhǎng)度,接著產(chǎn)生集合元素,把state設(shè)置為2,并最終返回true
- 當(dāng)sate為2的時(shí)候,也就是迭代器向前移動(dòng)一位,再次執(zhí)行MonveNext方法的時(shí)候,跳轉(zhuǎn)到state為2的語(yǔ)句塊部分,把索引位置自增1,再跳轉(zhuǎn)到state為1的語(yǔ)句塊中,產(chǎn)生新的集合元素
- 如此循環(huán)
yield break的用法
假設(shè)在一個(gè)無(wú)限循環(huán)的環(huán)境中獲取一個(gè)int類型的集合,在客戶端通過某個(gè)條件來終止循環(huán)。
class Program
{
static Random rand = new Random();
static IEnumerable<int> GetList()
{
while (true)
{
yield return rand.Next(100);
}
}
static void Main(string[] args)
{
foreach (int item in GetList())
{
if (item%10 == 0)
{
break;
}
Console.WriteLine(item);
}
Console.ReadKey();
}
}以上,當(dāng)集合元素可以被10整除的時(shí)候,就終止循環(huán)。終止循環(huán)的時(shí)機(jī)是在循環(huán)遍歷的時(shí)候。
如果用yield break,就可以在獲取集合的時(shí)候,當(dāng)符合某種條件就終止獲取集合。
class Program
{
static Random rand = new Random();
static IEnumerable<int> GetList()
{
while (true)
{
int temp = rand.Next(100);
if (temp%10 == 0)
{
yield break;
}
yield return temp;
}
}
static void Main(string[] args)
{
foreach (int item in GetList())
{
Console.WriteLine(item);
}
Console.ReadKey();
}
}總結(jié):
- yield return能返回一個(gè)"按需供給"的集合
- yield return是"語(yǔ)法糖",其背后是一個(gè)實(shí)現(xiàn)了IEnuerable,IEnumerator泛型、非泛型接口的類,該類維護(hù)著一個(gè)狀態(tài)字段,以保證yield return產(chǎn)生的集合能"按需供給"
- yield break配合yield return使用,當(dāng)產(chǎn)生集合達(dá)到某種條件的時(shí)候使用yield break,以終止繼續(xù)創(chuàng)建集合
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請(qǐng)查看下面相關(guān)鏈接
相關(guān)文章
C#串口通訊概念及簡(jiǎn)單的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于C#串口通訊概念及簡(jiǎn)單的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用C#具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
Unity實(shí)現(xiàn)模型點(diǎn)擊事件的方法
這篇文章主要介紹了Unity實(shí)現(xiàn)模型點(diǎn)擊事件的方法,本文通過多種方法給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-05-05
C#編程自學(xué)之?dāng)?shù)據(jù)類型和變量三
C#語(yǔ)言類型系統(tǒng)提出的一個(gè)核心概念裝箱(boxing)拆箱(unboxing)。裝箱和取消裝箱的概念是C#的類型系統(tǒng)的核心。它在“值類型”和“引用類型”之間的架起了一座橋梁,使得任何“值類型”的值都可以轉(zhuǎn)換為object類型的值,反過來轉(zhuǎn)換也可以。2015-10-10
c#擴(kuò)展datatable轉(zhuǎn)json示例
這篇文章主要介紹了c#擴(kuò)展datatable轉(zhuǎn)json示例,需要的朋友可以參考下2014-05-05
C#實(shí)現(xiàn)TCP客戶端和服務(wù)器的基本功能
本文將介紹如何使用C#實(shí)現(xiàn)TCP客戶端和服務(wù)器的基本功能,客戶端與服務(wù)器可以相互發(fā)送消息,文章通過代碼講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-12-12
C# 前端無(wú)插件打印導(dǎo)出實(shí)現(xiàn)方式詳解
本文講述了使用C#實(shí)現(xiàn)前端無(wú)插件的打印和導(dǎo)出功能,介紹了相關(guān)技術(shù)和方法,適合需要在項(xiàng)目中實(shí)現(xiàn)相應(yīng)功能的開發(fā)者參考2024-10-10

