引言
動態類型(Dynamic Types)可以向框架開發者提供高效的抽象編程模型,而不會產生通常因抽象而導致的性能損失。通過對面向接口編程和工廠設計模式的使用,可以開發一個框架,它既享有抽象編程模型通用的好處,同時也兼具硬編碼邏輯的性能優勢。
動態類型工廠使用程序基本元數據,確定以最佳的方式在運行時建立新類型。類代碼被直接“發出”到內存中的程序集中,無需通過.NET語言特定的編譯器編譯。類一旦被“發出”,它就已經被CLR“烘烤”好并隨時可供應用程序使用了。
這種方式看似只允許我們創建具有硬編碼邏輯的類,但其實是非常靈活的,因為你可以發出很多類,只要讓所有的類實現相同的接口即可。
通過.NET中的反射,你可以寫成千上萬的后期綁定抽象來建立通用的功能和框架。這些抽象對于企業開發者非常有價值,因為可以大大縮短開發時間。當寫一個通用模式就可以應用到所有的情況時,為什么還要把它的邏輯在十個不同的類中寫十次呢?
但是這些后期綁定抽象編程的應用程序通常會遭遇性能問題,這就是System.Reflection.Emit命名空間(以下簡稱Reflection.Emit)和動態類型有很大用途的地方。本文是由兩個部分組成的系列文章的第一部分,我會介紹什么是動態類型以及它的用途,實現和使用以及如何創建。我將會盡可能地闡述動態類型的用法并且給出示例代碼,完整的代碼包括動態類型的實現將在本系列文章的第二部分給出。
動態類型可能的用途
使用動態類型最常見的原因就是解決性能問題。ORM框架非常常見,它為映射類的屬性到數據庫表或存儲過程結果集提供通用的接口。大多數時候我們會使用某種形式的元數據比如XML將結果集中的列映射到類的屬性;為了做到這點,他們通過反射來查詢類所需的屬性,再使用反射將結果集的數據填充到類的屬性中。
這樣創建的ORM框架允許我們以更少的代碼簡單快速地添加新類,但是使用反射會導致嚴重的性能問題,相反,你可以使用動態類型創建一個ORM框架,由該動態類型創建類和用以填充數據的列之間的硬編碼映射邏輯。
究竟什么是動態類型
動態類型是在運行時手動生成的類或類型,并插入程序所在的AppDomain中。動態類型很酷的一點就是能評估一組給定的元數據新建一個根據當前情況進行優化過的類型,要做到這一點,就要使用Reflection.Emit命名空間提供的類創建新類型并將其功能直接“發出”。
使用Reflection.Emit創建動態類型的缺點是,不能直接將你的C#代碼直接“導入”動態程序集,然后通過C#編譯器編譯成IL中間碼,否則那就太簡單了。你必須使用Reflection.Emit提供的類來定義和生成類型,方法,構造器和屬性,然后插入或者“發出”IL操作碼到這些定義中。這會比正常編碼要困難一些,因為你必須使用IL中間碼,并且對它有大體的了解,但是話又說回來,也沒有想象的那么困難,后面我就會講到,有很多途徑可以讓它變得簡單。
定義接口
使用Reflection.Emit進行類型創建是我們面臨主要的問題,而且是一個很大的問題。但是在使用動態類型開發應用程序時,卻發現沒有相應的API對其進行編程,想想吧,在運行時生成的類在設計時并不存在,那么針對編寫針對動態類型的程序呢,沒錯,接口的魔力!
所以,第一步,先弄清楚動態類型的公共接口應該如何定義。讓我們來看一個例子。我之前提到的ORM框架,它將數據庫的字段映射到程序中的對象,你可以為每一個類建立一個映射功能,或者可以開發一個框架,根據它加載的類確定哪些字段應該賦值到哪些屬性。
所以我想這個接口應該是這樣的:
public interface IORMapper { void PopulateObject(object entity, DataRow data); }
動態類型生成器讀取XML文件內容創建新類型,并使用輸入的實體對象將新類型轉換為對應的類型,然后從DataRow中讀取相應列的數據賦值給實體對象的對應屬性,這樣動態類型就可以用來將數據填充到該類型的每一個對象中了。
是否已經存在這樣的ORM框架了呢?答案是肯定的,但是大多數ORM框架采用反射或者后期綁定策略完成數據庫列和對象屬性之間的映射,正如我之前所說,反射是最厲害的性能殺手之一。
動態類型工廠
接下來要做的就是設計用于生成的動態類型的類,它返回創建好的動態類型給調用者。對于動態類型生成器,工廠模式再合適不過了。當開發者想要隱藏如何創建一個實例的細節時,一般會選擇工廠模式,此模式通常用于這種情況,存在一個抽象基類或接口,并且有多個類繼承該基類或接口,而該類型的消費者不應該手動創建該類型的實例,所以必須調用工廠方法以決定創建哪個類的實例返回給消費者。非常棒的一個黑盒子,它隱藏了類型初始化邏輯,并且對于整個程序都沒有重復,這正是我們需要的,因為調用者無法顯式調用動態類型的構造器。而且我希望對調用者隱藏動態類型的實現細節,所以工廠類接口的代碼如下:
public class ORMapperFactory { public static IORMapper CreateORMapper(Type mappedType) { //method implementation } //private factory methods }
在我們的ORM框架中,CreateORMapper方法接受一個Type類型的參數,然后在XML映射文件中查找與該類型匹配的節點,該節點擁有一個內部節點集,它包含了所有列與對象屬性之間的映射關系。當工廠生成動態類型時,就會使用XML元數據來生成IL中間碼將輸入對象強制類型轉化為Mapper需要創建的類型,最后編碼將數據從DataRow中的列賦值給指定的屬性,就是這樣。
一旦生成了動態類型,所有需要從DataRow中獲取數據并賦值到該類型的對象都可以使用它,并且應用程序只需花費生成一次該類型的開銷。
下面的序列圖展示了它是如何工作的。首先,Consumer類調用ORMapperFactory請求IORMapper實例,然后將實參typeof(Customer)傳入,工廠類會通過它來決定ORMapper創建哪種類型的對象返回。然后Consumer調用新生成的ORMapper實例,并將其傳遞給Customer和DataRow。ORMapper通過硬編碼將數據從DataRow填充到Customer類的對應的屬性,然后Consumer就可以使用Customer類的屬性和值了。
建立動態類型
了解如何將IL中間碼發出到IORMapper.PopulateObject()方法之前,需要知道一些基本知識,首先,必須建立動態程序集以承載新的類型,因為Reflection.Emit無法向已有的程序集中添加新的類型,你必須通過AssemblyBuilder類在內存中生成一個全新的程序集。
private static AssemblyBuilder asmBuilder = null; private static ModuleBuilder modBuilder = null; private static void GenerateAssemblyAndModule() { if (asmBuilder == null) { AssemblyName assemblyName = new AssemblyName(); assemblyName.Name = "DynamicORMapper"; AppDomain thisDomain = Thread.GetDomain(); asmBuilder = thisDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); modBuilder = assBuilder.DefineDynamicModule( asmBuilder.GetName().Name, false); } }
要創建AssemblyBuilder實例,首先要創建AssemblyName實例并制定程序集的名稱,然后調用Thread.GetDomain()方法獲取AppDomain實例,該實例允許用DefineDynamicAssembly方法以指定名稱和訪問模式定義動態程序集,只需要向其傳入AssemblyName的實例和AssemblyBuilderAccess的枚舉值即可,這里我不想把程序集保存到文件中,如果想保存的話,只需使用枚舉值AssemblyBuilderAcess.Save或者AssemblyBuilderAcess.RunAndSave即可。
AssemblyBuilder完成創建之后,接下來要創建ModuleBuilder實例,它在后面被用來創建新的動態類型,然后使用AssemblyBuilder.DefineDynamicModule()方法創建一個新的動態模塊,當然你可以在動態程序集里創建任意多的動態模塊,但這里只需要創建一個。
非常幸運的是,一但AssemblyBuilder和ModuleBuilder創建成功,可以使用同樣的實例來創建任意多的動態類型,所以他們只需創建一次。
接下來,為了創建真正的動態類型,你必須創建一個TypeBuilder實例,下面的代碼創建了一個新的類型并且把它賦給了動態程序集:
private static TypeBuilder CreateType(ModuleBuilder modBuilder, string typeName) { TypeBuilder typeBuilder = modBuilder.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout, typeof(object), new Type[] {typeof(IORMapper)}); return typeBuilder; }
調用ModuleBuilder.DefineType()方法,創建TypeBuilder實例,第一個參數接受字符串類型的類型名稱,第二個參數接受TypeAttributes類型的枚舉,該枚舉定義了類型的所有屬性,第三個參數代表動態類型的父類型,本例中是System.Object,最后一個參數是動態類型實現的接口列表,這個參數非常重要,所以我將IORMapper傳遞給了該參數。
這里有一點需要指出,你是否有注意到我們通過Reflection.Emit中的類創建實例的一般模式呢?AppDomain用來創建AssemblyBuilder,AssemblyBuilder用來創建ModuleBuilder,ModuleBuilder用來創建TypeBuilder,這是工廠模式的另一個例子,在Reflection.Emit命名空間中經常用到。你或許已經猜到如何創建MethodBuilder,ConstructorBuilder,FieldBuilder,甚至是PropertyBuilder,沒錯,使用TypeBuilder!
我不想學IL中間碼
現在是時候深入使用Reflection.Emit中的類來寫一些IL中間碼了,如果你不希望花過多的時間去研究IL中間碼規范或者相關其他文檔,卻又因為工作不得不使用它,沒關系,微軟為我們在Visual Studio .NET中提供了一個工具ILDasm.exe,它會給你帶來巨大的幫助。
ILDasm可以讓你查看程序集內部結構,特別是元數據和IL中間碼,它們是程序集的重要組成部分,這就是我提到的在創建動態類型時,它會為我們提供巨大的幫助地方。與其去想如何通過IL中間碼生成動態類型,不如直接使用C#代碼寫出原型代碼,編譯到程序集中,并通過ILDasm反編譯出它的IL中間碼,要弄清楚這些IL中間碼的意圖就輕而易舉了,然后通過Reflection.Emit中的類將其重寫即可。ILDasm對于我學習和理解創建動態類型的來龍去脈提供了巨大的幫助。
雖然在創建動態類型的時候不需要理解IL中間碼,但是如果對IL中間碼的語法或和操作碼以及基于堆棧編程的原理有一些基本的理解,那么會對你非常有幫助。但是這里我不打算在涉及它,在文章的末尾我會推薦一些閱讀資料,它們對于我IL中間碼的學習起了很大的幫助。
Reflection.Emit“發出”方法剖析
無論是編寫方法、構造器、或者是屬性,本質上都是在編寫方法,這個方法包含一個實現了某個功能的代碼塊;此外,在使用Reflection.Emit定義這些類型的時候,還有一些地方需要注意。
在C#中,如果類型的默認構造函數中沒有任何邏輯,是不需要顯式定義的,C#編譯器會自動生成默認構造函數。同樣的,IL和Reflection.Emit中,ilasm.exe和TypeBuilder.CreateType()方法會在幕后為你打點好一切。在本例中,構造方法中沒有任何功能邏輯,但我還是來顯式定義它,因為這是一個很好的查看“發出”方法的示例,而且也足夠簡單。
創建TypeBuilder實例之后,我們需要創建ConstructorBuilder實例,TypeBuilder.DefineConstructor()方法接受3個參數:MethodAttributes枚舉,CallingConventions枚舉和表示構造函數的參數列表的Type類型數組,代碼如下:
ConstructorBuilder constructor = typeBuilder.DefineConstructor( MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.Standard, Type.EmptyTypes);
那么我如何知道在定義構造器時如何使用SpecialName和RTSpecialName的呢,我的確不知道,事實上我通過ILDasm.exe偷看了C#編譯器是如何創建構造方法的,下面的代碼是ILDasm.exe反編譯之后的代碼,它展示了C#編譯器是如何定義一個默認構造方法的:
.method public specialname rtspecialname instance void .ctor() cil managed { .maxstack 2 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret }
請注意,代碼中并未定義MethodAttributes.Instance的值,但是IL中間碼中有一個“實例”屬性分配給了構造函數的定義,這是因為MethodAttributes枚舉沒有定義該值,而是定義了MethodAttribtes.Static值,如果它的Static值未設置,那么ConstructorBuilder會默認設定為“實例”屬性。
傳入DefineConstructor()方法的第二個參數是CallingConventions.Standard,MSDN文檔中對于該枚舉的不同枚舉值之間的區別的資料很少,但是我可以告訴你的是,若果傳遞Standard值,CLR會為你指定合適的CallingConventions值,所以我總是使用該值。
最后一個傳入值是Type數組,所有的功能組建方法都有這個參數,它與方法定義的每個參數的類型相對應,數組中的每個參數類型必須與方法參數列表中的順序一致。由于默認構造函數沒有任何參數,所以我們使用Type.EmptyTypes,它是Type類中預定義的空數組。
下面是創建構造方法的完整代碼:
private static void CreateConstructor(TypeBuilder typeBuilder) { ConstructorBuilder constructor = typeBuilder.DefineConstructor( MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.Standard, new Type[0]); //Define the reflection ConstructorInfo for System.Object ConstructorInfo conObj = typeof(object).GetConstructor(new Type[0]); //call constructor of base object ILGenerator il = constructor.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, conObj); il.Emit(OpCodes.Ret); }
非常有趣的事情是,你并沒有從IL中得到很多的自由。在C#中,盡管每個類都顯示或者隱式繼承自System.Object類,但是你不需要顯示調用基類的構造方法(當然如果你想調用也可以沒有任何問題)。但是在IL中間碼中,必須顯示調用它,在本例中,我們需要調用System.Object的默認構造函數。
為了使用Reflection.Emit顯示調用默認構造函數,我們需要從創建ILGenerator實例開始,它是動態類型創建大部分工作的核心。ILGenerator的Emit()方法用于實際將IL操作碼發出到新建方法中,它的實例是通過你目前在使用的創建者(ConstructorBuilder, MethodBuilder, PropertyBuilder等)對象來建立的,Emit()有17個重載方法,我不會每個方法都過一遍,但是所有的重載方法都會接受一個OpCodes類的靜態屬性值作為第一個參數值,而第二個參數則根據具體的IL操作碼參數來決定是否需要傳入。
另外須要牢記的一點是,對于IL或者Reflection.Emit,創建實例方法的時候,每一個實例方法都有一個隱藏參數,它是對該方法所在類對象的引用,始終作為方法的第一個傳入參數。這就是我們可以在C#代碼中使用的this關鍵字,或者VB.NET中的Me關鍵字的原因,這非常重要,因為在任何時候,當需要調用類實例方法或字段時,都必須使用該參數引用調用類型的實例。另一個有關IL有趣的珍聞是,所有的參數總是從參數列表的位置索引,所以我剛才提到的隱藏參數總是處于索引位置0,方法顯式定義的參數總是從索引位置1開始。也就是說,每個實例方法至少有一個參數,包括默認構造函數和屬性getter。但是對于靜態方法而言,并沒有這個隱藏的參數,方法顯式定義的參數的引用索引位置從0開始。
IL中間碼和Reflection.Emit還有一個很大的區別,注意上面的IL代碼的第IL_001行,使用操作碼“call”并傳入“instance void [mscorlib]System.Object::.ctor()”,這句話的意思是調用System.Object實例的構造函數。如果要用Reflection.Emit,則需要使用反射調用System.Object的構造方法創建ConstructorInfo實例并將其傳遞給Emit()方法的第二個參數。
下面我們來看一下默認構造函數做了些什么。在這里我假設你已經對IL和基于堆棧的編程有了基本的了解。要調用Object類的構造函數,首先需要將隱藏參數this添加到棧中,然后使用“call”操作碼和ConstructorInfo實例,這等同于“this.base();”,在C#代碼中,直接調用System.Object的構造函數是非法的,但是在IL中間碼中,必須顯示調用,最后用操作碼OpCodes.Ret通知線程退出構造方法,該操作在每個方法的末尾都必須執行。
構造方法和其他一樣,唯一的區別是它沒有返回值。在IL中,當OpCodes.Ret被調用時,CLR會獲取被調用方計算堆棧棧頂的返回值并嘗試返回。如果在構造函數中,計算堆棧中有值存在,那就會出問題。如果獲取返回值時候,計算堆棧為空,你會得到一個“Common Language Runtime detected an invalid program”錯誤,更糟糕的是當你在運行程序時要調用該構造方法實例化一個對象的時候,系統才會報錯。(在運行之前驗證IL中間碼語法是否正確,參見本文“如何驗證我的IL是否正確”部分)。事實上在調用Ret操作碼時,上只要棧不為空,就會報錯。你可以通過下面的代碼來做一個測試:
il.Emit(OpCodes.Ldc_I4_3); //il.Emit(OpCodes.Pop);
運行測試程序,報錯了,對吧?代碼第一行表示將整數值3作為Int32類型推送到計算堆棧上。當調用操作碼Ret時,CLR發現數值3存在于計算堆棧中,但方法的返回類型卻是void,所以它拋出了異常。第二行注釋掉的代碼的作用是移除當前位于計算堆棧棧頂的值,取消注釋,執行Ret操作時,計算堆棧為空,構造方法就可以正確返回了。
為動態類型創建帶參數的構造函數、方法和屬性都可以遵循這個基本結構。對于DataRow的列到對象屬性映射的實現,我就不再贅述了,因為上文我已經闡述了所需的基本知識。記住最重要的一點,使用Reflection.Emit創建方法,最簡單的途徑是寫出對應的C#代碼,然后通過ILDasm.exe反編譯出IL中間碼,創建ILGenerator實例使用Emit方法將IL代碼發出到動態類型,但是要保證動態類型的IL中間碼和ILDasm反編譯出IL中間碼的結構完全一致。
創建和使用動態類型的實例
一切就緒,但是在創建動態類型之前,我想再簡單闡述一下工廠類如何創建動態類型實例并返回給調用者以及如何使用返回的動態類型:
TypeBuilder typeBuilder = null; public static IORMapper CreateORMapper(Type mappedType) { //check to see if type is already created if (typeBuilder == null) { //Didnt exist, so create assembly and module GenerateAssemblyAndModule(); //create new type for table name TypeBuilder typeBuilder = CreateType(modBuilder, "ORMapper_" + mappedType.Name); //create constructor CreateConstructor(typeBuilder); //create O/R populate object CreatePopulateObject(typeBuilder, mappedType); } Type mapperType = typeBuilder.CreateType(); try { IORMapper mapper = (IORMapper)mapperType.GetConstructor( Type.EmptyTypes).Invoke(Type.EmptyTypes); } catch (Exception ex) { //Log error if desired return null; } return mapper; }
工廠類首先檢查是否已經創建了TypeBuilder,如果還未創建,就調用之前定義的私有方法為動態類型創建DynamicAssembly,DynamicModule和TypeBuilder,構造函數和PopulateObject()方法。這一切都準備好之后,調用TypeBuilder.CreateType()創建一個Type類型的實例,通過該實例,調用CreateConstructor()方法,然后調用構造函數創建一個動態類型的工作實例。
到了關鍵的時刻了,創建一個新的類型并調用該類型的構造函數會調用之前通過IL建造的構造函數,任何錯誤的IL發出,都將導致CLR拋出異常。如果一切順利,ObjectMapper_<TypeName>實例就會成功創建,然后工廠方法會將它強制轉換為IORMapper類型并返回。
使用動態類型非常簡單,只需記住面向接口編程,因為類設計時并不存在。下面的代碼調用ObjectMapperFactory返回IORMapper實例。如果這是首次調用工廠類,它會產生ORMapper類并返回它的實例。之后工廠不再重新生成類,而是直接生成該類型的一個實例返回。然后用戶調用PopulateObject()方法傳入DataRow和Customer實例,代碼如下:
IORMapper mapper = ObjectMapperFactory.CreateObjectMapper(typeof(Customer)); foreach (DataRow row in data.Rows) { Customer c = new Customer(); mapper.PopulateObject(row, c); customers.Add(c); }
你也可對ORMapper使用工廠模式,然后只需要傳入DataRow,然后調用PopulateObject方法創建Customer對象并填充數據。
如何驗證我的IL
現在已經完成了動態類型工廠的創建,在Visual Studio中編譯通過。如果我運行它,它一定會工作嗎,那可不一定。Reflection.Emit的不足之處在于,可以發出任意你想要的IL組合,但是卻沒有設計時編譯器檢查你寫的IL是否有效。有時候通過TypeBuilder.CreateType()方法“烘烤”動態類型時,如果某些地方不對,它會報錯,但這只針對某些已知的問題。有時候只有當你第一次調用方法的時候才會報錯。還記得JIT編譯器嗎?它不會嘗試編譯和驗證IL直到第一次真正調用這個方法。事實上,你很可能不會發現你的IL是無效的,直到你真正運行你的程序,或者你第一次調用生成的動態類型。CLR會提供有效的錯誤提示信息,對吧?恐怕不會!我獲得過的最有幫助 的信息是“CLR運行時檢查到無效的程序”異常。
那么如何驗證IL中間碼的正確性呢,PEVerify.exe!它是一個.NET工具,用于檢查程序集IL中間碼、結構和元數據的有效性。要使用PEVerify.exe,必須將動態程序集保存到物理文件中(記住,到現在為止,動態程序集只存在于內存中)。為了將動態程序集保存到文件中,需要對之前的代碼做一些改動。首先,將AppDomain.DefineDynamicAssembly()方法傳遞的最后一個參數值修改為AssemblyBuilderAccess.RunAndSave;其次,修改AssemblyBuilder.DefineDynamicModule()方法的第二個參數傳入程序集名稱;最后在TypeBuilder.CreateType()方法后添加一行代碼將保存程序集到文件中,代碼如下:
Type draType = typeBuilder.CreateType(); assBuilder.Save(assBuilder.GetName().Name + ".dll");
一切就緒,運行應用程序并創建動態類型,在解決方案的Debug文件夾中會生成DynamicObjectMapper.dll文件(假設你使用的是Debug生成模式),現在打開.NET命令提示符窗口,輸入PEVerify <程序集的路徑>\DynamicObjectMapper.dll然后按下回車鍵,PEVerify將驗證程序集,并且最后告訴你是否有錯。PEVerify很棒的一點是它會提供非常詳盡的錯誤信息,包括是什么錯誤,哪里出錯了。另外需要注意的是,PEVerify驗證失敗并不意味著程序集一定無法運行。例如,當我第一次寫工廠類時,使用“callvirt”操作碼調用靜態方法String.Equals(),這導致PEVerify驗證失敗,但是它依然可以運行。這是一個非常簡單的錯誤,調用靜態方法時應該使用“call”操作碼而不是“callvirt“,修改后再次運行PEVerify就沒有出任問題了。
最后一點,如果修改了代碼將程序集保存到文件中,之后必須將其改為之前的代碼。這是因為一旦將動態程序集保存到文件中,它將被鎖定,將無法向其中添加新的動態類型。這是一個令人頭疼的問題,因為應用程序有可能根據不同的類型創建多個mapper對象,如果在第一個類型生成之后將它們保存到文件中,將引發異常。
Framework 2.0中的Emit
那么.NET 2.0中有什么新東西呢,有一個被稱為輕量級代碼生成(LCG)新功能非常不錯,它提供了一種更快的方式創建全局靜態方法,但是這里才是最酷的地方,你不需要創建動態程序集、動態模塊和動態類型來“發出”方法,你可以直接發出到主程序的程序集中!只需通過其6個構造函數(不需要工廠方法)創建一個新的DynamicMethod對象,接下來創建你的ILGenerator并發出你的IL操作碼,再調用這個方法,你也可以調用DynamicMethod.Invoke()方法或者使用Dynamic.CreateDelegate()方法獲取引用該動態方法的委托實例,最后任何時候你都可以調用它了。
LCG和.NET 2.0中的另一項新特性匿名方法似乎很相似,匿名方法是屬于全局靜態方法,不屬于任何類,并且作為委托暴露和調用。如果在幕后DynamicMethod類僅僅在程序集中創建了一個匿名方法就實現了LCG,我一點都不意外,因為DynamicMethod暴露出了返回委托類型的方法。
本文提出的解決方案一樣可以很容易地通過DynamicMethod類實現。因為它只是有一個公共方法的類,是使用LCG的完美候選者。只需要對工廠中的方法進行一點點的修改。你可以在設計一個DataRowAdapter類取代工廠創建的并返回給用戶的IDataRowAdapter實例,在該類中定義一個私有的委托類型變量和公有方法GetOrdinal(),該方法只調用委托的Invoke()方法然后獲取返回值。工廠類只負責創建DataRowAdapter和DynamicMethod實例,并從中獲取委托存儲到DataRowAdapter中。當用戶調用方法GetOrdinal,委托將會被調用,從而獲取整數序數并返回。
除此之外,Reflection.Emit命名空間還支持對泛型的動態創建。
閱讀延伸
如果想了解IL中間碼,你可以參看Simon Robinson的著作《Expert .NET 1.1 Programming》(這是一本非常棒的.NET著作)的前幾章。接下來,我推薦Jason Bock《CIL Programming:Under the Hood of .NET 》,全書都是和IL有關的編程,并且有幾章還詳細闡述了動態類型的創建,調試IL和動態類型。最后,如果你想更深入的學習IL,推薦《Inside Microsoft .NET IL Assembler》,作者是Serge Lindin,這本書是毋庸置疑的經典之作,他也正在準備這本書的第二版,將會覆蓋.NET 2.0的內容,而且定于今年5月發布。
本文為譯文,作者為jconwell,原文地址:Introduction to Creating Dynamic Types with Reflection.Emit
文章列表