反射不僅可以檢測已經存在的組件的型別資訊,也可以在執行階段,自行定義組件資訊,並回存到磁碟以重複使用。
建立自訂組件程式碼
在反射系統中,有一個子命名空間叫做 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

沒有留言:
張貼留言