yield關鍵字用法
引用自:探祕C#中的yield關鍵字 - Darren Ji - 部落格園 (cnblogs.com)
探祕C#中的yield關鍵字
在"C#中,什麼時候用yield return"中,我們瞭解到:使用yield return返回集合,不是一次性載入到記憶體中,而是客戶端每呼叫一次就返回一個集合元素,是一種"按需供給"。本篇來重溫yield return的用法,探祕yield背後的故事並自定義一個能達到yield return相同效果的類,最後體驗yield break的用法。
□ 回顧yield return的用法
以下程式碼建立一個集合並遍歷集合。
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也能獲得同樣的結果。修改GetList方法為:
static IEnumerable<int> GetList(int count)
{
for (int i = 0; i < count; i++)
{
yield return r.Next(10);
}
}
通過斷點除錯發現:客戶端每顯示一個集合中的元素,都會到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();
}
}
生成專案,並用Reflector反編譯可執行檔案。在.NET 1.0版本下檢視GetList方法,發現該方法返回的是一個GetList類的例項。原來yield return是"語法糖",其本質是生成了一個GetList的例項。
那GetList例項是什麼呢?點選Reflector中<GetList>連結檢視。
○ 原來GetList類實現了IEnumerable和IEnumerator的泛型、非泛型介面
○ yield return返回的集合之所以能被迭代、遍歷,是因為GetList內部有迭代器
○ yield return之所以能實現"按需供給",是因為GetList內部有一個_state欄位記錄這上次的狀態
接下來,就模擬GetList,我們自定義一個GetRandomNumbersClass類,使之能達到yield 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;//集合元素的數量
public int i; //當前指標
private int current;//儲存當前值
private int state;//儲存遍歷的狀態
#region 實現IEnumerator介面
public int Current
{
get { return current; }
}
public bool MoveNext()
{
switch (state)
{
case 0: //即為初始預設值
i = 0;//把指標調向0
goto case 1;
break;
case 1:
state = 1;//先設定原狀態
if (!(i < count))//如果指標大於等於當前集合元素數量
{
return false;
}
current = Program.r.Next(10);
state = 2; //再設定當前狀態
return true;
break;
case 2: //再次遍歷如果state值為2
i++;//指標再移動一位
goto case 1;
break;
}
return false;
}
//被顯式呼叫的屬性
object IEnumerator.Current
{
get { return Current; }
}
public void Reset()
{
throw new NotImplementedException();
}
public void Dispose()
{
}
#endregion
#region 實現IEnumerable的泛型和非泛型
public IEnumerator<int> GetEnumerator()
{
return this;
}
//被顯式呼叫的屬性
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
}
關於GetRandomNumbersClass類:
○ count表示集合的長度,可以在客戶端賦值。當呼叫迭代器的MoveNext方法,需要把count和當前位置比較,以決定是否可以再向前移動。
○ 欄位i相當於索引,指標每次移動一位,i需要自增1
○ current表示當前儲存的值,外部通過IEnumerator.Current屬性訪問
迭代器的MoveNext方法是關鍵:
○ state欄位是整型,表示產生集合過程中的3種狀態
○ 當state為0的時候,說明是初始狀態,把索引位置調到0,並跳轉到state為1的部分
○ 當state為1的時候,首先把狀態設定為1,然後判斷索引的位置有沒有大於或等於集合的長度,接著產生集合元素,把state設定為2,並最終返回true
○ 當sate為2的時候,也就是迭代器向前移動一位,再次執行MonveNext方法的時候,跳轉到state為2的語句塊部分,把索引位置自增1,再跳轉到state為1的語句塊中,產生新的集合元素
○ 如此迴圈
□ yield break的用法
假設在一個無限迴圈的環境中獲取一個int型別的集合,在客戶端通過某個條件來終止迴圈。
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();
}
}
以上,當集合元素可以被10整除的時候,就終止迴圈。終止迴圈的時機是在迴圈遍歷的時候。
如果用yield break,就可以在獲取集合的時候,當符合某種條件就終止獲取集合。
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();
}
}
總結:
○ yield return能返回一個"按需供給"的集合
○ yield return是"語法糖",其背後是一個實現了IEnuerable,IEnumerator泛型、非泛型介面的類,該類維護著一個狀態欄位,以保證yield return產生的集合能"按需供給"
○ yield break配合yield return使用,當產生集合達到某種條件的時候使用yield break,以終止繼續建立集合