从编译器层面理解C#中的闭包的这个坑!
前言
在公眾號上看到一篇文章《正確使用和理解C#中的閉包》,里面提到了閉包的一個(gè)坑:
當(dāng)捕獲的外部變量為for循環(huán)的迭代變量時(shí),C#認(rèn)為變量i是定義在循環(huán)體外的。所以,當(dāng)添加委托集合的for循環(huán)執(zhí)行完時(shí),i的值已經(jīng)變?yōu)?了;因此,我們在foreach中循環(huán)調(diào)用委托時(shí),i的值就都是3了。
List<Action>?levyActions?=?new?List<Action>(); for?(int?i?=?0;?i?<?3;?i++) {levyActions.Add(()=>?i.Dump()); } foreach?(Action?action?in?levyActions) {action(); }那么,明明是循環(huán)體內(nèi)定義的變量i,為什么會被認(rèn)為定義在循環(huán)體外呢?
編譯器魔法——Lowering
我們知道,C#代碼最終會編譯成IL中間語言。
假設(shè)有一個(gè)數(shù)組:
int[]?arr?=?new[]?{?0,?1,?2?};我們可以有多種方式遍歷它:
//1 foreach?(var?i1?in?arr) {i1.Dump(); } //2 for?(var?i2?=?0;?i2?<?arr.Length;?i2++) {var?value?=?arr[i2];value.Dump(); } //3 var?i3?=?0; while(i3<?arr.Length) {var?value?=?arr[i3];value.Dump();i3++; }那么,是不是要對應(yīng)準(zhǔn)備3種IL語法呢?
其實(shí)不是,在編譯之前編譯器還會施展一個(gè)魔法:Lowering
大概意思是,讓編譯器從高級語言功能“降低”到同一語言中的低級語言功能。
怎么理解這句話呢?讓我們打開https://sharplab.io/
Roslyn編譯器實(shí)現(xiàn)
sharplab.io這個(gè)網(wǎng)站可以顯示.NET代碼(比如c#)的編譯中間過程和結(jié)果。
我們將上面的C#代碼復(fù)制到窗口左邊:
可以看到,編譯器會將foreach和for語法都轉(zhuǎn)換成while語法,這樣,編譯器最后只需要實(shí)現(xiàn)一種IL語法即可。
除了迭代以外,在roslyn編譯器中實(shí)現(xiàn)了很多的“Lowering”,比如:
異步重寫器
Lambda重寫器
狀態(tài)機(jī)重寫器
詳細(xì)列表你可以查看“https://github.com/dotnet/roslyn/tree/main/src/Compilers/CSharp/Portable/Lowering”下的代碼。
結(jié)論
現(xiàn)在,大家應(yīng)該已經(jīng)知道,for循環(huán)中的變量i實(shí)際會被轉(zhuǎn)換成while循環(huán)外定義的變量num,因此i在循環(huán)體作用域外也是有效的,導(dǎo)致了閉包的這個(gè)坑。
知道了原理,解決方案也很簡單,始終使用循環(huán)體內(nèi)的變量即可:
for?(var?i?=?0;?i?<?3;?i++) {var?j?=?i;levyActions.Add(()?=>?j.Dump()); }如果你覺得這篇文章對你有所啟發(fā),請關(guān)注我的個(gè)人公眾號”My IO“
總結(jié)
以上是生活随笔為你收集整理的从编译器层面理解C#中的闭包的这个坑!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浅谈.Net异步编程的前世今生----T
- 下一篇: C# 使用 HelpProvider 控