反射不僅可以檢測已經存在的組件的型別資訊,也可以在執行階段,自行定義組件資訊,並回存到磁碟以重複使用。
建立自訂組件程式碼
在反射系統中,有一個子命名空間叫做 Emit ( System.Reflection.Emit )。 在 Emit 中有一組 builder 類別,是用來建立組件、型別、方法等物件。 在執行時期要產生程式碼,必須循序漸進,先建立組件,再建立組件中的模組,最後是模組中的型別。 每一個builder類別都是由它本身的info類別部分繼承而來。 例如, AssebmlyBuilder 類別是繼承 AssemblyInfo , MethodBuilder 類別是繼承 MethodInfo 。
建立一個組件的步驟:
就是一步一步來:
- 1. 建立 AssemblyBuilder :透過 AppDomain 類別的 DefineDynamicAssembly 方法,建立動態組件 ( AssemblyBuilder )。
- 2. 建立 ModuleBuilder :利用建立的 AssemblyBuilder ,產生模組物件( ModuleBuilder )。
- 3. 建立 TypeBuilder :利用建立的 ModuleBuilder 物件中的 DefineType 方法,來建立動態型別 ( TypeBuilder )。
- 4. 建立其他成員Builder:使用 TypeBuilder 提供的方法,以建立各類的成員。如: ConstructorBuilder 、 MethodBuilder ...。
- 5. 建立程式碼:由 ConstructorBuilder.GetILGenerator 建立一個 ILGenerator 物件來撰寫動態程式碼。
AppDomain.DefineDynamicAssembly 多載方法
//使用[指定的名稱]、[存取模式]來定義動態組件。 public AssemblyBuilder DefineDynamicAssembly(AssemblyName name, AssemblyBuilderAccess access); //使用[指定的名稱]、[存取模式]和[儲存目錄]來定義動態組件。 public AssemblyBuilder DefineDynamicAssembly(AssemblyName name, AssemblyBuilderAccess access, string dir); ...
AssemblyName 建構子
public AssemblyName(); public AssemblyName(string assemblyName);
AssemblyBuilderAccess 列舉
在建立動態組件時,必須使用 AssemblyBuilderAccess 列舉,指明組件的存取模式。
- Run:動態組件可以執行,但不能儲存。
- Save:動態組件可以儲存,但不能執行。
- RunAndSave:動態組件可以執行及儲存。
- ReflectionOnly:動態組件已載入至僅反射的內容中,而且無法執行。
- RunAndCollect:動態組件可以卸載而且其記憶體可以回收
AssemblyBuilder 類別的方法
AssemblyBuilder.Save(string assemblyFileName);
注意:
上面這一行是儲存組件時叫用的方法,不過它的參數值內容,會受到 DefineDynamicAssembly 建構子的引響:
- 若要將組件儲存到指定的目錄,則必須使用有指定儲存目錄的 DefineDynamicAssembly 建構子。
- 若使用沒有指定路徑的 DefineDynamicAssembly 建構子,則此處 assemblyFileName 參數就不能含有磁碟機或目錄名稱。
下列程式碼示範,如何建立組件
//================================================================================================ //建立 Dynamic Assembly (使用AssemblyBuilderAccess指定組件的存取模式。) //================================================================================================ AssemblyBuilder myAsmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( new AssemblyName("DemoAssembly"), AssemblyBuilderAccess.RunAndSave ); //================================ //建立 Module //================================ ModuleBuilder myModuleBuilder = myAsmBuilder.DefineDynamicModule("DemoModule", "DemoAssembly.dll"); //================================ //建立 Type (使用TypeAttributes指定型別屬性。) //================================ TypeBuilder myTypeBuilder = myModuleBuilder.DefineType("DemoClass", TypeAttributes.Public | TypeAttributes.Class); //================================ //建立 Field //================================ //DefineField(para1,para2,para3) => (欄位名稱,欄位型別,欄位的屬性標籤) // private int _count; FieldBuilder myField = myTypeBuilder.DefineField("_count", typeof(int), FieldAttributes.Private); //================================ // 建立 constructor // DefineConstructor:用來"定義建構式", // GetConstructor:用來取得"可實際被呼叫的建構式方法" //================================ //建立一個具有個int參數的建構子 Type[] args = { typeof(int) }; ConstructorBuilder myConstructorBuilder = myTypeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, args); //產生 MSIL 指令碼. ILGenerator IL_generator = myConstructorBuilder.GetILGenerator(); //================================================================================================ // 以上完成動態組件的建立,但是不含程式碼。必須透過 ILGenerator.Emit() 方法,將 IL Code 放置進去 //================================================================================================ // 放置指令到指令資料流中 // 以下指令相當於建立C#程式碼 : _count = x IL_generator.Emit(OpCodes.Ldarg_0); // load arg0 IL_generator.Emit(OpCodes.Ldarg_1); // load arg1 IL_generator.Emit(OpCodes.Stfld, myField); // 以新值 arg1 取代指定的參考物件 myField ( _count) IL_generator.Emit(OpCodes.Ret); // 回傳 IL 程式碼 //================================ //建立 Method 程式碼 //================================ // 放置指令到指令資料流中 // 以下指令相當於建立C#程式碼 : _count = _count + x Type[] AddArgs = { typeof(int) }; MethodBuilder myMethodBuilder = myTypeBuilder.DefineMethod("Add", MethodAttributes.Public, null, AddArgs); ILGenerator ilAdd = myMethodBuilder.GetILGenerator(); ilAdd.Emit(OpCodes.Ldarg_0); // ilAdd.Emit(OpCodes.Dup); ilAdd.Emit(OpCodes.Ldfld, myField); ilAdd.Emit(OpCodes.Ldarg_1); ilAdd.Emit(OpCodes.Add); ilAdd.Emit(OpCodes.Stfld, myField); ilAdd.Emit(OpCodes.Ret); //================================ //建立 Property //1.建立屬性◦ //2.建立屬性的存取欄位。 //3.建立屬性存取欄位方法。 //================================ //(para1,para2,para3,para4) => (屬性名稱,屬性標籤,傳回型別,參數列型別) // int Count PropertyBuilder myProperty = myTypeBuilder.DefineProperty("Count", PropertyAttributes.None, typeof(int), new Type[] { typeof(int) }); //================================ //建立 Property 的存取方法 //================================ //定義存取方法的屬性,其中要做為類別屬性的存取方法,必需要有特別的屬性,亦即要有MethodAttributes.SpecialName及MethodAttributes.HideSig MethodAttributes getsetAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; // GET //定義取得方法,(para1,para2,para3,para4) => (方法名稱,描述方法的屬性,回傳的型別,傳入參數列型別) MethodBuilder mbGetCount = myTypeBuilder.DefineMethod("get_Count", getsetAttributes, typeof(int), null); //定義取得方法的中介語言,第二行是代表將我們先前定義的FieldBuilder欄位值傳回。 ILGenerator ilGetCount = mbGetCount.GetILGenerator(); ilGetCount.Emit(OpCodes.Ldarg_0); // ldarg.0 holds the "this" reference ilGetCount.Emit(OpCodes.Ldfld, myField); ilGetCount.Emit(OpCodes.Ret); // SET //定義設定方法,(para1,para2,para3,para4) => (方法名稱,描述方法的屬性,回傳的型別,傳入參數列型別) MethodBuilder mbSetCount = myTypeBuilder.DefineMethod("set_Count", getsetAttributes, null, new Type[] { typeof(int) }); //定義設定方法的中介語言,第三行是將傳入值設定到先前定義的FieldBuilder欄位。 ILGenerator ilSetCount = mbSetCount.GetILGenerator(); ilSetCount.Emit(OpCodes.Ldarg_0); ilSetCount.Emit(OpCodes.Ldarg_1); ilSetCount.Emit(OpCodes.Stfld, myField); ilSetCount.Emit(OpCodes.Ret); //在定義好存取及設定方法後,接下來將方法加入到屬性當中。 myProperty.SetGetMethod(mbGetCount); myProperty.SetSetMethod(mbSetCount); //================================ //建立型別 //================================ myTypeBuilder.CreateType(); //================================ //儲存組件 //================================ myAsmBuilder.Save(@"DemoAssembly.dll"); //========================================================= //若不儲存組件資訊,我們也可以直接使用目前建立的型別 //========================================================= //取得型別,建立 Constructor Type myType = myTypeBuilder.CreateType(); ConstructorInfo creater = myType.GetConstructor(new Type[]{typeof(int)}); object TypeInstance = creater.Invoke(new object[] { 20 }); //取得 GetCount 方法 MethodInfo mGetCount = myType.GetMethod("get_Count"); //查看目前的值 Console.WriteLine(((int)mGetCount.Invoke(TypeInstance, null)).ToString()); //20 //取得 SetCount 方法,並且 Invoke MethodInfo mSetCount = myType.GetMethod("set_Count"); mSetCount.Invoke(TypeInstance, new object[] { 10 }); //查看目前的值 Console.WriteLine(((int) mGetCount.Invoke(TypeInstance,null)).ToString()); //10 //取得 Add 方法,並且 Invoke MethodInfo mAdd = myType.GetMethod("Add"); mAdd.Invoke(TypeInstance, new object[] { 25 }); //查看目前的值 Console.WriteLine(((int)mGetCount.Invoke(TypeInstance, null)).ToString()); //35
下圖:我們用ILDasm這個工具來反組譯上面程式碼所產生的Dll。
下圖:IL程式碼中 Constructor 的定義。
下圖:IL程式碼中 Add 方法的定義。
下圖:我們用 Telerik Decompiler 這個反組譯工具,來看看 Reflectoin 後的程式碼。
將上面程式碼建立的組件加入參考後,我們寫一段code來驗證一下這個組件型別是否可以正常使用:
DemoClass demo = new DemoClass(15); Console.WriteLine(demo.get_Count()); //15 demo.set_Count(25); Console.WriteLine(demo.get_Count()); //25 demo.Add(10); Console.WriteLine(demo.get_Count()); //35
沒有留言:
張貼留言