在這個大數據/雲計算/人工智慧研發普及的時代,Python的崛起以及Javascript的前後端的侵略,程式員與企業似乎越來越青睞動態語言所帶來的便捷性與高效性,即使靜態語言在性能,錯誤檢查等方面的優於靜態語言。對於.NETer來說,.NET做為一門靜態語言,我們不僅要打好.NET的基本功,如基本類 ...
在這個大數據/雲計算/人工智慧研發普及的時代,Python的崛起以及Javascript的前後端的侵略,程式員與企業似乎越來越青睞動態語言所帶來的便捷性與高效性,即使靜態語言在性能,錯誤檢查等方面的優於靜態語言。對於.NETer來說,.NET做為一門靜態語言,我們不僅要打好.NET的基本功,如基本類型/語法/底層原理/錯誤檢查等知識,也要深入理解.NET的一些高級特性,來為你的工作減輕負擔和提高代碼質量。
ok,咱們今天開始聊一聊.NET中的Emit。
一、什麼是Emit?
Emit含義為發出、產生的含義,這是.NET中的一組類庫,命名空間為System.Reflection.Emit,幾乎所有的.NET版本(Framework/Mono/NetCore)都支持Emit,可以實現用C#代碼生成代碼的類庫
二、Emit的本質
我們知道.NET可以由各種語言進行編寫,比如VB,C++等,當然絕大部分程式員進行.NET開發都是使用C#語言進行的,這些語言都會被各自的語言解釋器解釋為IL語言並執行,而Emit類庫的作用就是用這些語言來編寫生成IL語言,並交給CLR(公共語言運行時)進行執行。
我們先來看看IL語言長什麼樣子:
(1) 首先我們創建一個Hello,World程式
class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); } }
(2) 將程式編譯成dll文件,我們可以看到在開發目錄下生成了bin文件夾
(3) 向下尋找,我們可以看到dll文件已經生成,筆者使用netcore3進行開發,故路徑為bin/Debug/netcoreapp3.0
(4) 這時候,我們就要祭出我們的il查看神器了,ildasm工具
如何找到這個工具?打開開始菜單,找到Visual Studio文件夾,打開Developer Command Prompt,在打開的命令行中鍵入ildasm回車即可,筆者使用vs2019進行演示,其它vs版本操作方法均一致
(5) 在dasm菜單欄選擇文件->打開,選擇剛剛生成的dll文件
(6) 即可查看生成il代碼
有了ildasm的輔助,我們就能夠更好的瞭解IL語言以及如何編寫IL語言,此外,Visual Studio中還有許多插件支持查看il代碼,比如JetBrains出品的Resharper插件等,如果覺得筆者方式較為麻煩可以使用以上插件查看il代碼
三、理解IL代碼
在上一章節中,我們理解了Emit的本質其實就是用C#來編寫IL代碼,既然要編寫IL代碼,那麼我們首先要理解IL代碼是如何進行工作的,IL代碼是如何完成C#當中的順序/選擇/迴圈結構的,是如何實現類的定義/欄位的定義/屬性的定義/方法的定義的。
IL代碼是一種近似於指令式的代碼語言,與彙編語言比較相近,所以習慣於寫高級語言的.NETer來說比較難以理解
讓我們來看看Hello,World程式的IL代碼:
IL_0000: nop IL_0001: ldstr "Hello World!" IL_0006: call void [System.Console]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret
我們可以把IL代碼看成棧的運行
第一條指令,nop表示不做任何事情,表示代碼不做任何事情
第二條指令,ldstr表示將字元串放入棧中,字元串的值為“Hello,World!”
第三條指令,call表示調用方法,參數為調用方法的方法信息,並把返回的結構壓入棧中,使用的參數為之前已經入棧的“Hello World!”,以此類推,如果方法有n個參數,那麼他就會調取棧中n個數據,並返回一個結果放回棧中
第四條指令,nop表示不做任何事情
第五條指令,ret表示將棧中頂部的數據返回,如果方法定義為void,則無返回值
關於Hello,world程式IL的理解就說到這裡,更多的指令含義讀者可以參考微軟官方文檔,筆者之後也會繼續對Emit進行講解和Emit的應用
四、用Emit類庫編寫IL代碼
既然IL代碼咱們理解的差不多了,咱們就開始嘗試用C#來寫IL代碼了,有了IL代碼的參考,咱們也可以依葫蘆畫瓢的把代碼寫出來了
(1) 引入Emit命名空間
using System.Reflection.Emit;
(2) 首先我們定義一個Main方法,入參無,返回類型void
//定義方法名,返回類型,輸入類型 var method = new DynamicMethod("Main", null, Type.EmptyTypes);
(3) 生成IL代碼
//生成IL代碼 var ilGenerator = method.GetILGenerator(); ilGenerator.Emit(OpCodes.Nop); ilGenerator.Emit(OpCodes.Ldstr,"Hello World!"); ilGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) })); //尋找Console的WriteLine方法 ilGenerator.Emit(OpCodes.Nop); ilGenerator.Emit(OpCodes.Ret);
(4) 創建委托並調用
//創建委托 var helloWorldMethod = method.CreateDelegate(typeof(Action)) as Action; helloWorldMethod.Invoke();
(5)運行,即輸出Hello World!
五、小結
Emit的本質是使用高級語言生成IL代碼,進而進行調用的的一組類庫,依賴Emit我們可以實現用代碼生成代碼的操作,即編程語言的自舉,可以有效彌補靜態語言的靈活性的缺失。
Emit的性能非常好,除了第一次構建IL代碼所需要時間外,之後只要將操作緩存在電腦記憶體中,速度與手寫代碼相差無幾
有許多著名.NET類庫均依賴於Emit:
(.NET JSON操作庫)Json.NET/Newtonsoft.Json: github地址
(輕量ORM)Dapper:gituhb地址
(ObjectToObjectMapper)EmitMapper:github地址
(AOP庫)Castle.DynamicProxy:github地址
學習Emit:
.NET官方文檔:https://docs.microsoft.com/zh-cn/dotnet
.NET API瀏覽器:https://docs.microsoft.com/zh-cn/dotnet/api
之後作者將繼續講解.NET Emit的相關內容和應用,感謝閱讀