在本文中說明.NET Emit技術,簡單的來說Emit的技術可以在執行時期動態的建立Assembly、Class,是.NET提供相當彈性的技術。 建立Assembly
要動態的建立Assembly有三個步驟,第一是要建立AssemblyName物件,AssemblyName用來表達Assembly的名稱, 其有參數的建構式只有一個,亦即傳入Assembly的名字;AssemblyName還有一些有用的屬性,例如可用Version來取得 Assembly的版本編號。 接下來取得目前執行的AppDomain以用來建立Assembly,要取得AppDomain有兩種方法,第一種是透過 Thread.GetDomain()方法來取得,另外一種方法則是用AppDomain.CurrentDomain屬性來取得;AppDomain簡 單的說可以是當成是應用程式執行的環境,可以包含多個Assembly。 在取得AppDomain後即可呼叫DefineDynamicAssembly來動態建立Assembly,只需傳入AssemblyName物件;並且指定執行型態,分為四種:
在有了Assembly的實體後,接下來建立Module,Module(模組)是諸如type.dll或application.exe 等類型可移植的執行檔,包含一個或多個類別 (Class) 和介面。多個命名空間可能包含在單一模組中,並且命名空間可以擴展多個模組。 有個重要的觀念要說明,呼叫DefineDynamicAssembly會傳回AssemblyBuilder物件,而可用 AssemblyBuilder來呼叫DefineDynamicModule方法取得ModuleBuilder;也就是說我們運用Emit來定義一個 動態的實體,通常是呼叫Define相關的方法,而會傳回對應的Builder類別,而可再用Builder類別再去仔細定義其下的內容。 理解這樣的觀念對於Emit要熟悉就輕鬆多了。 建立Class取得了ModuleBuilder後,終於可以運用ModuleBuilder來建立類別,我們呼叫ModuleBuilder的
DefineType來建立類別;第一個參數是類別名稱,第二個參數是類別的屬性,第三個參數是此動態類別所繼承的類別;另外可傳入第四個參數為實作的介
面列表。
下面為最常用的DefineType方法,亦即上述提到可傳入四個參數。
此處要注意的是若是要取得一般物件的型別(System.Type)可用GetType()方法,但是若本身就是型別,要取得其系統型別,那麼要用關鍵字typeof(型別)來取得,以供Reflection及Emit所使用。 建立Constructor在取得了TypeBuilder物件後,即可用此物件來建立建構式,雖然物件屬性被標示為AutoClass會自動建立建構式;但是面對像Emit 這樣底層的技術,我會建議還是走過一次如何建立建構式的過程,而且若是有繼承類別,並且在父類別的建構式中有程式碼要執行,那麼最好也是熟過自行建立建構 式,以確保會呼叫到父代的類別。 我們看到以下的程式碼,先呼叫DefineConstructor建立建構式,傳入建構式的屬性,留意在最後一個參數傳入Type.EmptyTypes代表是無參數建構式,或是可以傳入型別代表建構式的傳入參數。 接下來的程式碼做仔細的拆解,其中typeBuilder.BaseType是取得繼承的父類別型態,再用GetConstructor取得父類別的建構式實體,傳入的參數為Type.EmptyTypes,亦即父代也是空白建構式。 需要注意的觀念是DefineConstructor是用來"定義建構式",而GetConstructor則是用來取得"可實際被呼叫的建構式方法",若是這觀念清楚,相信Emit與Reflection的觀念也就相當清楚。 接下來透過constructor取得其ILGenerator,亦即透過ILGenerator物件可以實際定義中介語言的內容,重點在 於定義的第二行呼叫il.Emit(OpCodes.Call,conObj);亦即直接呼叫剛才我們取得的父代Constructor方法,用這樣的方 式可以確保是呼叫到正確的父代建構式。
建立Property在上段我們建立了建構式,接下來我們要建立類別的欄位;雖然類別的屬性是AutoClass會自動產生欄位,不過我的建議是使用Emit這種接近底層的技術,最好實際走過一次細節,若是要自行詳細定義屬可分為三部份:
我們首先看到前兩點,在語法上都不難,一樣的我們都是呼叫TypeBuilder的變數來建立;下列程式碼第一行是建立屬性,第二行是建立欄位。
.NET Emit技術初探(下) |