[英]Need help understanding C# yield in IEnumerable
我正在閱讀C#2010 Accelerated。 我不知道什么是yield
調用GetEnumerator時,包含yield語句的方法中的代碼實際上並未在該時間點執行。 相反,編譯器生成枚舉器類,該類包含yield塊代碼
public IEnumerator<T> GetEnumerator() {
foreach( T item in items ) {
yield return item;
}
}
我也讀了一些幫助理解“產量”
yield是一個懶惰的數據生成器,只在檢索到第一個后生成另一個項目,而返回一個列表將一次性返回所有內容。
這是否意味着每次調用GetEnumerator
都會從集合中獲得1個項目? 所以第一個電話我得到第一個項目,第二個,我得到第二個,依此類推......?
想到它的最佳方式是當你第一次從IEnumerator
請求一個項目時(例如在foreach
),它開始通過該方法運行,當它達到yield return
它會暫停執行並返回該項目供你使用你的foreach
。 然后你請求下一個項目,它恢復它離開的代碼並重復循環,直到它遇到yield break
或方法結束。
public IEnumerator<string> enumerateSomeStrings()
{
yield return "one";
yield return "two";
var array = new[] { "three", "four" }
foreach (var item in array)
yield return item;
yield return "five";
}
看一下IEnumerator<T>
接口; 這很可能是為了澄清正在發生的事情。 編譯器將您的代碼轉換為實現IEnumerable<T>
和IEnumerator<T>
。 對GetEnumerator()的調用只返回類本身。
該實現基本上是一個狀態機,對於每次調用MoveNext(),它執行代碼直到下一個yield return
,然后將Current設置為返回值。 foreach
循環使用此枚舉器遍歷枚舉項,在循環的每次迭代之前調用MoveNext()。 編譯器在這里做了一些非常酷的事情,使yield return
語言中最強大的構造之一。 從程序員的角度來看,這只是一種根據要求懶惰退貨的簡單方法。
是的,這是正確的,來自MSDN的例子說明了如何使用它
public class List
{
//using System.Collections;
public static IEnumerable Power(int number, int exponent)
{
int counter = 0;
int result = 1;
while (counter++ < exponent)
{
result = result * number;
yield return result;
}
}
static void Main()
{
// Display powers of 2 up to the exponent 8:
foreach (int i in Power(2, 8))
{
Console.Write("{0} ", i);
}
}
}
/*
Output:
2 4 8 16 32 64 128 256
*/
如果我理解你的問題是正確的,那么你的理解是錯誤的,我很害怕。 yield語句(yield return和yield break)是一個非常聰明的編譯器技巧。 您方法中的代碼實際上被編譯為實現IEnumerable的類。 該類的一個實例是該方法將返回的內容。 讓我們在調用ins.GetEnumerator()時調用實例'ins',你會得到一個IEnumerator,當每個調用MoveNext()生成集合中的下一個元素時(yield返回負責這個部分),當序列沒有更多的元素時(例如遇到yield break)MoveNext()返回false,進一步調用會導致異常。 因此,GetEnumerator的調用不是生成(下一個)元素而是調用MoveNext
理解yield關鍵字的簡單方法是,當使用yield return關鍵字返回時,我們不需要額外的類來保存迭代的結果。 通常,當我們遍歷集合並想要返回結果時,我們使用集合對象來保存結果。 我們來看看例子吧。
public static List Multiplication(int number,int times)
{
List<int> resultList = new List<int>();
int result = number;
for(int i=1;i<=times;i++)
{
result=number*i;
resultList.Add(result);
}
return resultList;
}
static void Main(string [] args)
{
foreach(int i in Multiplication(2,10))
{
Console.WriteLine(i);
}
Console.ReadKey();
}
在上面的例子中,我想返回2次乘法的結果。 所以我創建一個方法乘法,它返回2倍的乘法,並將結果存儲在列表中,當我的main方法調用乘法方法時,控件循環遍歷循環十次並將結果結果存儲在列表中。 這是沒有使用收益率回報。 假設我想使用yield return執行此操作,它看起來像
public static IEnumerable Multiplication(int number,int times)
{
int result = number;
for(int i=1;i<=times;i++)
{
result=number*i;
yield return result;
}
}
static void Main(string [] args)
{
foreach(int i in Multiplication(2,10))
{
Console.WriteLine(i);
}
Console.ReadKey();
}
現在在Multiplication方法中有輕微的變化,返回類型是IEnumerable,並且沒有其他列表來保存結果,因為使用Yield返回類型必須是IEnumerable或IEnumerator,並且由於Yield提供有狀態迭代,我們不需要額外的類來保存結果。 因此在上面的例子中,當從Main方法調用Multiplication方法時,它計算第一次迭代的結果並將結果返回到main方法並返回到循環並計算第二次迭代的結果並將結果返回給main方法這樣,Yield在每次迭代中將結果返回到調用方法一個接一個。還有其他關鍵字中斷與Yield結合使用會導致迭代停止。 例如,在上面的例子中,如果我想計算乘法只有一半的次數(10/2 = 5),那么方法如下所示:
public static IEnumerable Multiplication(int number,int times)
{
int result = number;
for(int i=1;i<=times;i++)
{
result=number*i;
yield return result;
if (i == times / 2)
yield break;
}
}
這個方法現在將導致乘以2,5倍。希望這有助於您理解Yield的概念。 有關更多信息,請訪問http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx
看起來你明白了。
如你所描述的那樣,在你的類的GetEnumerator
使用yield
,這樣你就可以編寫如下代碼:
foreach (MyObject myObject in myObjectCollection)
{
// Do something with myObject
}
通過從第一個調用返回第一個項目,從第二個調用返回第二個項目,依此類推,您可以遍歷集合中的所有元素。
yield
在MyObjectCollection
定義。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.