在先前的文章有提到過「在執行時期憑空捏造一個組件」這件事,我是用在自己打造的 Library 之中,而做這件事的最主要目的是在執行時期產生一個類別,用來產生使用端類別的替身,或是用來做一些特殊的識別,我們就來看一下,要在執行時期產生一個類別,需要做哪些事情?
首先我們要知道,屬性(Property)、欄位(Field)、方法(Method)是隸屬於類別(Type),會成為類別的成員(Member),而類別隸屬於模組(Module),模組則隸屬於組件(Assembly),因此我們必須先從 Assembly 開始一路建下去。
產生組件
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("MyAssembly"), AssemblyBuilderAccess.Run);
產生模組
var moduleBuilder = assemblyBuilder.DefineDynamicModule("MyAssembly");
產生類別
var typeBuilder = moduleBuilder.DefineType("MyType", TypeAttributes.Public);
產生屬性
var propertyBuilder = typeBuilder.DefineProperty("MyProperty", PropertyAttributes.HasDefault, typeof(string), null);
產生欄位
var fieldBuilder = typeBuilder.DefineField("myField", typeof(string), FieldAttributes.Private);
產生方法
var methodBuilder = typeBuilder.DefineMethod("MyMethod", MethodAttributes.Public, typeof(int), new[] { typeof(int), typeof(int) });
var ilGenerator = methodBuilder.GetILGenerator();
// 傳入 2 個 int 相加之後回傳
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Ldarg_2);
ilGenerator.Emit(OpCodes.Add);
ilGenerator.Emit(OpCodes.Ret);
為何載入參數的索引從 1 開始? 因為索引 0 的參數預設是 this。
最後,我們只要呼叫 TypeBuilder.CreateType()
方法就能建立類別,透過 Type.InvokeMember()
方法就可以簡單測試一下我們所建立的方法,執行結果正不正確?
不過,因為這些都是在執行時期才會產生的,所以我們沒辦法在編譯的時候就預先知道錯誤,再來就是我們沒有該類別實際上的程式碼,所以也不容易除錯,這些都是我們要使用這種方式建立類別所要考量的。
完整的程式碼
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("MyAssembly"), AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("MyAssembly");
var typeBuilder = moduleBuilder.DefineType("MyType", TypeAttributes.Public);
var propertyBuilder = typeBuilder.DefineProperty("MyProperty", PropertyAttributes.HasDefault, typeof(string), null);
var fieldBuilder = typeBuilder.DefineField("myField", typeof(string), FieldAttributes.Private);
var methodBuilder = typeBuilder.DefineMethod("MyMethod", MethodAttributes.Public, typeof(int), new[] { typeof(int), typeof(int) });
var ilGenerator = methodBuilder.GetILGenerator();
// 傳入 2 個 int 相加之後回傳
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Ldarg_2);
ilGenerator.Emit(OpCodes.Add);
ilGenerator.Emit(OpCodes.Ret);
var myType = typeBuilder.CreateType();
var myObj = Activator.CreateInstance(myType);
var result = myType.InvokeMember(
"MyMethod",
BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public,
null,
myObj,
new object[] { 1, 2 });