1. 程式人生 > >.NET高階特性-Emit(1)

.NET高階特性-Emit(1)

  在這個大資料/雲端計算/人工智慧研發普及的時代,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的相關內容和應用,感謝