.NET Emit 入門教程:第四部分:構建型別(Type)

路过秋天發表於2024-03-25

前言:

在動態生成程式碼的過程中,構建型別(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 來動態建立型別,包括定義欄位、方法、屬性和事件等,

幫助讀者理解如何在執行時生成和操作型別資訊。

相關文章