前言:
在動態生成程式碼的過程中,構建型別(Type)是至關重要的一步。
透過使用 Emit 中的 TypeBuilder,我們可以定義和建立各種型別,包括類、結構體和介面。
本節將深入探討如何使用 TypeBuilder 動態構建型別,並介紹其在實際應用中的重要性。
定義公用程式碼,生成程式集以供對照:
透過學習本系列之前的文章,我們可以輕鬆定義 AssemblyBuilder 程式集構建器,再透過程式集構建器,定義 ModuleBuilder 模組構建器。
下面我們先透過定義公用程式碼來生成程式集,以便更好的透過反編繹,來觀察對照我們生成的程式碼。
AssemblyName assName = new AssemblyName("myAssembly"); AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(assName, AssemblyBuilderAccess.RunAndSave); ModuleBuilder mb = ab.DefineDynamicModule("myModule","a.dll"); //...今天的示例程式碼存放地 ab.Save("a.dll");
注意標紅的部分為 .NET 版本程式碼,正如本系列之前檔案所說,只有 .NET 版本支援程式集持久化,.NET Core 需要到9版本才支援。
.NET Core 用 AssemblyBuilder.DefineDynamicAssembly來構建。
ModuleBuilder 的幾個定義方法:
1、定義列舉:
EnumBuilder eb=mb.DefineEnum("bbb", ...);
2、定義類(包括類、介面、結構體):
TypeBuilder tb=mbDefineType("aaa", ...);
3、定義內部類:
TypeBuilder tb=mbDefineType("aaa", ...); TypeBuilder innerClassBuilder = tb.DefineNestedType("innerClass",...);
下面我們使用程式碼對照,來學習本節內容:
1、定義列舉:
EnumBuilder eb = mb.DefineEnum("MyNameSpace.MyEnum", TypeAttributes.Public, typeof(int)); eb.DefineLiteral("Spring", 0); eb.DefineLiteral("Summer", 1); eb.DefineLiteral("Autumn", 2); eb.DefineLiteral("Winter", 3); Type enumType = eb.CreateType();
對應生成的程式碼:
2、定義介面:
TypeBuilder tb = mb.DefineType("MyNameSpace.MyInterface", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Interface); //tb.DefineField("ID", typeof(int), FieldAttributes.Public| FieldAttributes.Static| FieldAttributes.InitOnly); // 定義屬性 "Name",型別為 int PropertyBuilder propertyBuilder = tb.DefineProperty("Name", PropertyAttributes.None, typeof(int), null); // 定義屬性的 getter 方法 MethodBuilder getterMethodBuilder = tb.DefineMethod("get_Name", MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual, typeof(int), Type.EmptyTypes); propertyBuilder.SetGetMethod(getterMethodBuilder); // 定義屬性的 setter 方法 MethodBuilder setterMethodBuilder = tb.DefineMethod("set_Name", MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual, null, new Type[] { typeof(int) }); propertyBuilder.SetSetMethod(setterMethodBuilder);
//定義方法 GetMyName MethodBuilder getMyName = tb.DefineMethod("GetMyName", MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual, typeof(string), new Type[] { typeof(int) }); tb.CreateType();
屬性的定義,需要掛接 get_XXX 和 set_XXX 兩個方法,會相對顯的定義麻煩了點。
對應生成的程式碼:
3、定義結構體
// 定義結構體 TypeBuilder tb = mb.DefineType("MyNameSpace.MyStruct", TypeAttributes.SequentialLayout | TypeAttributes.Public | TypeAttributes.Sealed, typeof(ValueType)); // 定義欄位 tb.DefineField("ID", typeof(int), FieldAttributes.Public); tb.DefineField("Name", typeof(string), FieldAttributes.Public); tb.CreateType();
對應生成的程式碼:
4、定義類:抽象類
TypeBuilder tb = mb.DefineType("MyNameSpace.MyClassBase", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Class); tb.DefineField("ID", typeof(int), FieldAttributes.Public); tb.DefineMethod("MyProtectedMethod", MethodAttributes.Family | MethodAttributes.Abstract | MethodAttributes.Virtual, typeof(void), Type.EmptyTypes); tb.CreateType(); tb.CreateType();
MethodAttributes.Family 對應的即:protected 修飾符。
對應生成的程式碼:
5、定義類:並繼承自抽象類:
//定義基類
TypeBuilder tb = mb.DefineType("MyNameSpace.MyClassBase", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Class); tb.DefineField("ID", typeof(int), FieldAttributes.Public); tb.DefineMethod("MyProtectedMethod", MethodAttributes.Family | MethodAttributes.Abstract | MethodAttributes.Virtual, typeof(void), Type.EmptyTypes); Type typeBase = tb.CreateType();
//定義子類,繼承基類 TypeBuilder tbClass = mb.DefineType("MyNameSpace.MyClass", TypeAttributes.Public | TypeAttributes.Class, typeBase); //實現抽象方法 MethodBuilder mbClass = tbClass.DefineMethod("MyProtectedMethod", MethodAttributes.Family | MethodAttributes.Virtual, typeof(void), Type.EmptyTypes); ILGenerator iL = mbClass.GetILGenerator(); iL.Emit(OpCodes.Ret); tbClass.CreateType();
紅色標註為指定繼承,介面繼承一樣在該引數指定。
對應生成的程式碼:
6、定義類:增加泛型引數指定
//定義基類 TypeBuilder tb = mb.DefineType("MyNameSpace.MyClassBase", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Class); tb.DefineField("ID", typeof(int), FieldAttributes.Public); tb.DefineMethod("MyProtectedMethod", MethodAttributes.Family | MethodAttributes.Abstract | MethodAttributes.Virtual, typeof(void), Type.EmptyTypes); Type typeBase = tb.CreateType(); //定義子類繼承基類 TypeBuilder tbClass = mb.DefineType("MyNameSpace.MyClass", TypeAttributes.Public | TypeAttributes.Class, typeBase); //實現抽象方法 MethodBuilder mbClass = tbClass.DefineMethod("MyProtectedMethod", MethodAttributes.Family | MethodAttributes.Virtual, typeof(void), Type.EmptyTypes); ILGenerator iL = mbClass.GetILGenerator(); iL.Emit(OpCodes.Ret); // 定義泛型引數 string[] typeParamNames = { "T" }; GenericTypeParameterBuilder[] typeParams = tbClass.DefineGenericParameters(typeParamNames); //定義泛型方法 MethodBuilder methodBuilder = tbClass.DefineMethod("GetT", MethodAttributes.Public, typeParams[0], new Type[] { typeof(object) }); ILGenerator ilGenerator = methodBuilder.GetILGenerator(); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Ret); tbClass.CreateType();
這裡透過定義泛型引數,來指定我們的泛型類。
對應生成的程式碼:
7、透過內部類定義委託:
// 定義內部類,並在內部類中定義委託型別 TypeBuilder delegateBuilder = tbClass.DefineNestedType("MyNameSpace.AuthDelegate", TypeAttributes.Class | TypeAttributes.NestedPublic | TypeAttributes.Sealed, typeof(MulticastDelegate)); // 新增委託的建構函式 ConstructorBuilder constructor = delegateBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { typeof(object), typeof(IntPtr) }); constructor.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed); // 新增Invoke方法 delegateBuilder.DefineMethod("Invoke", MethodAttributes.Public, typeof(bool), new Type[] { typeof(string) }).SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed); // 建立內部類和委託型別 Type authDelegateType = delegateBuilder.CreateType();
注意,這裡是透過Type的形式,來定義委託。
因此,我們對其限定名稱空間,限定其使用範圍:
同時將委託定義在某個類當成員變數:
透過定義事件,是使用委託的方式之一。
8、定義事件:
//定義事件 EventBuilder eb = tbClass.DefineEvent("MyEvent", EventAttributes.None, delegateBuilder); MethodBuilder addMethod = tbClass.DefineMethod("add_OnAuth", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName, typeof(void), new Type[] { delegateBuilder }); ILGenerator addMethodIL = addMethod.GetILGenerator(); //...... addMethodIL.Emit(OpCodes.Ret); eb.SetAddOnMethod(addMethod); MethodBuilder removeMethod = tbClass.DefineMethod("remove_OnAuth", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName, typeof(void), new Type[] { delegateBuilder }); ILGenerator removeMethodIL = removeMethod.GetILGenerator(); //...... removeMethodIL.Emit(OpCodes.Ret); eb.SetRemoveOnMethod(removeMethod);
注意事項:
1、定義事件,透過特殊方法: DefineEvent 來定義。
2、定義事件,第三個事件引數Type,需要傳遞 delegateBuilder ,則不是 delegateType,否則會報錯:
3、定義事件,需要同時掛兩個對應的新增和移除方法,否則,執行正常,但反編繹會報錯:
4、定義方法,傳遞的委託型別,和注意事項2一致,需要傳遞 delegateBuilder,否則一樣的錯誤資訊。
下面檢視正常情況下的反繹繹生成程式碼:
對委託和事件的定義,一個神奇的Bug:
透過反編繹 ILSpy 軟體,可以看到已經定義成功了,但透過引用生成的程式集,即發現裡面沒有相關的委託或事件產生?
同時透過 VS2022 自帶的反編繹【直接F12跳轉】,裡面也沒有任何相關的委託或事件程式碼?
總結
構建型別是動態程式碼生成過程中的關鍵一環,透過靈活運用 TypeBuilder 和相關工具,
我們可以實現各種複雜型別的動態生成,為程式的靈活性和可擴充套件性提供有力支援。
總的來說,本章節透過演示如何使用 Emit 來動態建立型別,包括定義欄位、方法、屬性和事件等,
幫助讀者理解如何在執行時生成和操作型別資訊。