翻譯|使用教程|編輯:王香|2018-10-12 10:27:49.000|閱讀 307 次
概述:本文用實際案例講解了yield return運(yùn)算符的工作原理。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
相關(guān)鏈接:
【下載FastReport.Net最新版本】
yield return運(yùn)算符是使用C#的程序員中最不為人知的運(yùn)算符之一。甚至那些了解它的人也不能完全確定他們正確理解其工作原理。必須糾正這個惱人的差距。而且,我希望這篇文章可以幫助你。
yield return運(yùn)算符返回迭代器中的集合項,并將當(dāng)前位置移動到下一個元素。yield return運(yùn)算符的存在將該方法轉(zhuǎn)換為迭代器。每次迭代器遇到y(tǒng)ield return時,它都會返回一個值。此運(yùn)算符向我們和編譯器發(fā)出信號,表示此表達(dá)式是迭代器。迭代器的任務(wù)是在集合的元素之間移動并返回當(dāng)前元素的值。許多人習(xí)慣于在循環(huán)中調(diào)用計數(shù)器作為迭代器,但事實并非如此,因為計數(shù)器不返回值。迭代器由編譯器轉(zhuǎn)換為“有限狀態(tài)機(jī)”,跟蹤當(dāng)前位置并知道如何“移動”到下一個位置。在這種情況下,序列元素的值在訪問它時計算。
這是迭代器的最簡單示例:
public static IEnumerable<int> GetItems() { foreach (var i in List) { yield return i; } }
迭代器只能返回IEnumerable <>類型。
迭代器是更復(fù)雜的枚舉器模式的語法快捷方式。當(dāng)C#編譯器遇到迭代器時,它會將其內(nèi)容擴(kuò)展為實現(xiàn)枚舉器模式的CIL代碼。這種封裝大大節(jié)省了程序員的時間。迭代器允許您執(zhí)行所謂的“延遲計算”。這意味著僅在請求時才評估元素的值。為了更好地理解收益率回報如何運(yùn)作,我們將其與傳統(tǒng)周期進(jìn)行比較 通過示例,一切都變得清晰。
(1)請注意,使用yield return,我們不需要創(chuàng)建額外的列表來填充值。因此我們節(jié)省了內(nèi)存,因為我們只需要內(nèi)存用于集合的當(dāng)前元素。元素處理不分配內(nèi)存,只分配緩存。
static IEnumerable<int> GetSequence() { Random rand = new Random(); List<int> list = new List<int>(); for (int i = 0; i < 3; i++) list.Add(rand.Next()); return list; } static IEnumerable<int> GetSequence() { Random rand = new Random(); for (int i = 0; i < 3; i++) yield return rand.Next(); }
(2)不計算整個枚舉結(jié)果的能力。在這個例子中,我們無限地生成數(shù)字:
IEnumerable<int> GetInfinityWithIterator() { var i = 0; while (true) yield return ++i; } IEnumerable<int> GetInfinityWithLoop() { var i = 0; var list = new List<int>(); while (true) list.Add(++i); return list; }
下面來看看他們之間的差異:
foreach(var item in GetInfinityWithIterator().Take(5)) { Console.WriteLine(item); }
我們使用LINQ運(yùn)算符Take來限制樣本量。在收益率返回的情況下,循環(huán)在第五個元素處停止。
foreach(var item in GetInfinityWithLoop().Take(5)) { Console.WriteLine(item); }
你不能打斷列表填寫。結(jié)果,我們得到錯誤的內(nèi)存不足。
(3)執(zhí)行迭代器后調(diào)整集合值的能力。 由于yield在實際處理時返回一個集合元素(例如,當(dāng)在控制臺中顯示元素的值時),即使在執(zhí)行迭代器之后,我們也可以更改集合的元素。調(diào)用它時,迭代器實際上不會返回實際值。迭代器知道從哪里獲取值。只有當(dāng)他們真的需要時,他才會歸還他們。這就是所謂的懶惰負(fù)載。
IEnumerable<int> MultipleYieldReturn(IEnumerable<int> mass) { foreach (var item in mass) yield return item * item; } IEnumerable<int> MultipleLoop(IEnumerable<int> mass) { var list = new List<int>(); foreach (var item in mass) list.Add(item * item); return list; }
將調(diào)用這些方法:
var mass = new List<int>() { 1, 2, 3 }; var MultipleYieldReturn = Helper.MultipleYieldReturn(mass); var MultipleLoop = Helper.MultipleLoop(mass); mass.Add(4); Console.WriteLine(string.Join(",",MultipleYieldReturn)); Console.WriteLine(string.Join(",", MultipleLoop));
結(jié)果是:
初始化MultipleYieldReturn和MultipleLoop變量后,我們再向集合中添加一個元素:mass.Add(4);
Console.WriteLine(string.Join(",",MultipleYieldReturn)); Console.WriteLine(string.Join(",", MultipleLoop));
結(jié)果:
在將結(jié)果輸出到控制臺時,集合包含值4.由于yield return在查詢時生成值,因此迭代器處理了所有當(dāng)前值。在初始化MultipleLoop變量時運(yùn)行傳統(tǒng)循環(huán),此時集合僅包含3個值。
(4)具有收益率回報的異常處理具有細(xì)微差別。yield return語句不能在try-catch部分中使用,只能在try-finally中使用。例如,如何在不知道約束的情況下編寫:
public IEnumerable TransformData(List<string> data) { foreach (string item in data) { try { yield return PrepareDataRow(item); } catch (Exception ex) { Console.Error.WriteLine(ex.Message); } } }
在這種情況下,catch塊永遠(yuǎn)不會捕獲錯誤。這都是延遲執(zhí)行收益率回報的原因。我們只在使用迭代器的數(shù)據(jù)進(jìn)行實際工作時才了解錯誤。例如,當(dāng)我們將數(shù)據(jù)從迭代器輸出到控制臺時。在此之前,迭代器不適用于實際數(shù)據(jù)。
如果你仍然需要“Catch”這個迭代器中的錯誤,那么你可以這樣做:
public IEnumerable TransformData(List<string> data) { string text; foreach (string item in data) { try { text = PrepareDataRow(item); } catch (Exception ex) { Console.Error.WriteLine(ex.Message); continue; } yield return text; } }
yield break類似于break運(yùn)算符,它只在迭代器中使用。這是一個小例子:
IEnumerable<int> GetNumbers() { int i = 0; while (true) { if (i = 5) yield break; yield return i++; } }
從示例中可以清楚地看出,當(dāng)達(dá)到5的值時,迭代器將結(jié)束,但在此之前它將正確返回值。
總結(jié)一下,什么時候應(yīng)該使用yield return?
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn