【FastReport.Net】什么是yield return?
【下載FastReport.Net最新版本】
yield return運(yùn)算符是使用C#的程序員中最不為人知的運(yùn)算符之一。甚至那些了解它的人也不能完全確定他們正確理解其工作原理。必須糾正這個(gè)惱人的差距。而且,我希望這篇文章可以幫助你。
yield return運(yùn)算符返回迭代器中的集合項(xiàng),并將當(dāng)前位置移動(dòng)到下一個(gè)元素。yield return運(yùn)算符的存在將該方法轉(zhuǎn)換為迭代器。每次迭代器遇到y(tǒng)ield return時(shí),它都會(huì)返回一個(gè)值。此運(yùn)算符向我們和編譯器發(fā)出信號(hào),表示此表達(dá)式是迭代器。迭代器的任務(wù)是在集合的元素之間移動(dòng)并返回當(dāng)前元素的值。許多人習(xí)慣于在循環(huán)中調(diào)用計(jì)數(shù)器作為迭代器,但事實(shí)并非如此,因?yàn)橛?jì)數(shù)器不返回值。迭代器由編譯器轉(zhuǎn)換為“有限狀態(tài)機(jī)”,跟蹤當(dāng)前位置并知道如何“移動(dòng)”到下一個(gè)位置。在這種情況下,序列元素的值在訪(fǎng)問(wèn)它時(shí)計(jì)算。
這是迭代器的最簡(jiǎn)單示例:
public static IEnumerable<int> GetItems() { foreach (var i in List) { yield return i; } }
迭代器只能返回IEnumerable <>類(lèi)型。
迭代器是更復(fù)雜的枚舉器模式的語(yǔ)法快捷方式。當(dāng)C#編譯器遇到迭代器時(shí),它會(huì)將其內(nèi)容擴(kuò)展為實(shí)現(xiàn)枚舉器模式的CIL代碼。這種封裝大大節(jié)省了程序員的時(shí)間。迭代器允許您執(zhí)行所謂的“延遲計(jì)算”。這意味著僅在請(qǐng)求時(shí)才評(píng)估元素的值。為了更好地理解收益率回報(bào)如何運(yùn)作,我們將其與傳統(tǒng)周期進(jìn)行比較 通過(guò)示例,一切都變得清晰。
(1)請(qǐng)注意,使用yield return,我們不需要?jiǎng)?chuàng)建額外的列表來(lái)填充值。因此我們節(jié)省了內(nèi)存,因?yàn)槲覀冎恍枰獌?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)不計(jì)算整個(gè)枚舉結(jié)果的能力。在這個(gè)例子中,我們無(wú)限地生成數(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; }
下面來(lái)看看他們之間的差異:
foreach(var item in GetInfinityWithIterator().Take(5)) { Console.WriteLine(item); }
我們使用LINQ運(yùn)算符Take來(lái)限制樣本量。在收益率返回的情況下,循環(huán)在第五個(gè)元素處停止。
foreach(var item in GetInfinityWithLoop().Take(5)) { Console.WriteLine(item); }
你不能打斷列表填寫(xiě)。結(jié)果,我們得到錯(cuò)誤的內(nèi)存不足。
(3)執(zhí)行迭代器后調(diào)整集合值的能力。 由于yield在實(shí)際處理時(shí)返回一個(gè)集合元素(例如,當(dāng)在控制臺(tái)中顯示元素的值時(shí)),即使在執(zhí)行迭代器之后,我們也可以更改集合的元素。調(diào)用它時(shí),迭代器實(shí)際上不會(huì)返回實(shí)際值。迭代器知道從哪里獲取值。只有當(dāng)他們真的需要時(shí),他才會(huì)歸還他們。這就是所謂的懶惰負(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變量后,我們?cè)傧蚣现刑砑右粋€(gè)元素:mass.Add(4);
Console.WriteLine(string.Join(",",MultipleYieldReturn)); Console.WriteLine(string.Join(",", MultipleLoop));
結(jié)果:
在將結(jié)果輸出到控制臺(tái)時(shí),集合包含值4.由于yield return在查詢(xún)時(shí)生成值,因此迭代器處理了所有當(dāng)前值。在初始化MultipleLoop變量時(shí)運(yùn)行傳統(tǒng)循環(huán),此時(shí)集合僅包含3個(gè)值。
(4)具有收益率回報(bào)的異常處理具有細(xì)微差別。yield return語(yǔ)句不能在try-catch部分中使用,只能在try-finally中使用。例如,如何在不知道約束的情況下編寫(xiě):
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)不會(huì)捕獲錯(cuò)誤。這都是延遲執(zhí)行收益率回報(bào)的原因。我們只在使用迭代器的數(shù)據(jù)進(jìn)行實(shí)際工作時(shí)才了解錯(cuò)誤。例如,當(dāng)我們將數(shù)據(jù)從迭代器輸出到控制臺(tái)時(shí)。在此之前,迭代器不適用于實(shí)際數(shù)據(jù)。
如果你仍然需要“Catch”這個(gè)迭代器中的錯(cuò)誤,那么你可以這樣做:
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類(lèi)似于break運(yùn)算符,它只在迭代器中使用。這是一個(gè)小例子:
IEnumerable<int> GetNumbers() { int i = 0; while (true) { if (i = 5) yield break; yield return i++; } }
從示例中可以清楚地看出,當(dāng)達(dá)到5的值時(shí),迭代器將結(jié)束,但在此之前它將正確返回值。
總結(jié)一下,什么時(shí)候應(yīng)該使用yield return?
- 列出對(duì)象時(shí)。迭代器將比返回的集合更快地工作。并且內(nèi)存開(kāi)銷(xiāo)較低;
- 在無(wú)限循環(huán)中。使用Take()方法,您始終可以限制選擇。