.NET Emit 入門教程:第五部分:動態生成方法(MethodBuilder 與 DynamicMethod)

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

前言:

當我們涉及到在執行時生成和定義方法時,便需要使用到C#中的兩個關鍵類之一:MethodBuilder 或 DynamicMethod。

這兩者都屬於反射(Reflection.Emit)的一部分,允許我們以動態的方式建立方法。

兩者各有側重,使用方式大體相同,本篇文章我們先介紹 MethodBuilder,再介紹 DynamicMethod,最後再總結兩者的區別。

1、MethodBuilder 介紹:

MethodBuilder 是一個強大的工具,用於在動態程式集中建立方法。

如果你需要構建整個型別(包括欄位、屬性、方法等),那麼按流程:

首先你需要建立一個動態程式集(AssemblyBuilder),

然後在其中建立一個模組(ModuleBuilder),

最後再建立一個或多個型別(TypeBuilder)。

而要在這些型別中建立方法,就可以使用 MethodBuilder

其關鍵特點:

  • 繫結到型別MethodBuilder 建立的方法是屬於某個型別的一部分,因此只能透過該型別的例項或靜態引用來呼叫。
  • 方法簽名:需要指定方法名稱、引數型別和返回型別。
  • IL程式碼生成:需要手動編寫IL程式碼。

2、MethodBuilder 程式碼:定義方法

正如上文所說,MethodBuilder 的使用,需要定義整個程式集上下文。

因此我們先編寫一下共用部分程式碼,同樣用於( .NET 版本生成程式集,以便對照生成程式碼):

AssemblyName assName = new AssemblyName("myAssembly") { Version = new Version("1.1.1.2") };
AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(assName, AssemblyBuilderAccess.RunAndSave);
ModuleBuilder mb = ab.DefineDynamicModule("myModule", "b.dll");
TypeBuilder tb = mb.DefineType("MyNameSpace.MyClass", TypeAttributes.Public | TypeAttributes.Class);


//定義方法......


tb.CreateType();
ab.Save("b.dll");

方法定義的過程:

1、透過 TypeBuilder 的 DefineMethod 來定義方法:MethodBuilder methodBuilder = tb.DefineMethod("方法名",......);
2、透過建構函式,可以設定方法定義的引數;
3、也可以後面再透過 MethodBuilder 例項 的 SetXXX 及  系列來定義引數。

下面示例展示方法的定義:

A、定義例項方法:使用簡單引數

 //定義例項方法:透過建構函式指定方法修飾符:Public、返回值:typeof(string)、引數型別:typeof(int),typeof(string)
 MethodBuilder methodBuilder = tb.DefineMethod("MyMethod", MethodAttributes.Public, typeof(string), new Type[] { typeof(int), typeof(string) });
//定義引數的名稱:指定引數名稱:id,name methodBuilder.DefineParameter(
1, ParameterAttributes.None, "id"); methodBuilder.DefineParameter(2, ParameterAttributes.None, "name"); //用IL編寫方法實現 var il = methodBuilder.GetILGenerator(); il.EmitWriteLine("hello world!"); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ret);
對照生成的程式碼:

注意事項:

1:DefineParameter 對引數的索引從1開始(對例項方法或靜態方法都一樣)。
2:IL 構建程式碼時:例項方法下 Ldarg_0 是 this 自身,靜態方法下 Ldarg_0 是id引數。

B、定義靜態方法:引數進階【包括ref、out、指標】大雜繪

//定義靜態方法
MethodBuilder methodBuilder = tb.DefineMethod("MyMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(object), new Type[] { typeof(int).MakeByRefType(), typeof(string).MakePointerType() });
methodBuilder.DefineParameter(1, ParameterAttributes.None, "id");
methodBuilder.DefineParameter(2, ParameterAttributes.Out | ParameterAttributes.Optional, "name");

var il = methodBuilder.GetILGenerator();
il.EmitWriteLine("hello world!");
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ret);

對照生成的程式碼:

簡要說明:

1、ref 引數定義:在建構函式型別用:typeof(int).MakeByRefType()
2、指標 引數定義:在建構函式型別用:typeof(int).MakePointerType()
3、out 引數定義:在 DefineParameter 方法的 ParameterAttributes.Out 引數指定。
注意事項:在靜態方法中,IL 程式碼 Ldarg_0 指向第一個引數 id。

C、定義方法:新增特性

 // 新增自定義特性
 ConstructorInfo attributeConstructor = typeof(AssemblyTitleAttribute).GetConstructor(new Type[] { typeof(string) });
 CustomAttributeBuilder attributeBuilder = new CustomAttributeBuilder(attributeConstructor, new object[] { "ExampleAttribute" });
 methodBuilder.SetCustomAttribute(attributeBuilder);

對照生成的程式碼:

3、MethodBuilder 程式碼:方法的動態呼叫

對於執行時生成的動態方法實現動態呼叫,可以透過反射呼叫。

下面演示(將方法變更為例項方法,使用反射呼叫):

透過獲取類的定義Type classType,然後Type.GetMethod獲取方法進行呼叫,並傳進建立的例項作為引數之一。

當然,如果方法呼叫頻率很高,要更進一步,也可以為 MethodInfo 建立委託,來實現更高效率的呼叫。

然而:

Delegate.CreateDelegate(......) 只支援從靜態方法轉委託方法,不支援例項方法。 

所以:

預設是不能實現對例項 MethodInfo 轉委託的高效呼叫的。

但,那是預設,經過本人的百日創新,結合 DynamicMethod,還是有辦法實現的。

實現方式可以參考本人的開源框架 Taurus.MVC 中關於 Deletgate 的相關實現。

既然提到了 DynamicMethod,那下面就開始介紹它了。

4、DynamicMethod 介紹:

DynamicMethod 則更加靈活。它允許在執行時生成和執行方法,而無需建立動態程式集或動態型別來容納該方法。

這意味著你可以直接生成和執行少量程式碼,而不必擔心整個型別的構建。

關鍵特點:

  • 不繫結到型別DynamicMethod 不屬於特定的型別,因此可以在任何上下文中呼叫。
  • 效能:通常比 MethodBuilder 更高效。

5、DynamicMethod 程式碼:

 //建立動態方法
 DynamicMethod dynamicMethod = new DynamicMethod("MyMethod", typeof(void), null);
 var il = dynamicMethod.GetILGenerator();
 il.EmitWriteLine("hello world!");
 il.Emit(OpCodes.Ret);

 //建立呼叫委託
 var deletegateMethod = dynamicMethod.CreateDelegate(typeof(Action)) as Action;
 //執行委託
 deletegateMethod();

執行結果:

簡要說明:

1、DynamicMethod 擁有的基礎定義方法和 MethodBuilder 基本一致。
2、DynamicMethod 動態方法關注點是執行時執行,因此,可以省略很多引數的定義,只需要定義最簡單的方法名、返回值、輸入引數。
3、DynamicMethod 預設建立的靜態方法,因此,它可以預設擁有 CreateDelegate 來實現高效呼叫。

6、DynamicMethod 與 MethodBuilder 兩者的區別:

在C#中,MethodBuilderDynamicMethod都屬於反射發射(Reflection.Emit)的一部分,用於在執行時生成和定義方法。

讓我們來簡要介紹一下它們之間的區別:

  1. DynamicMethod:

    • DynamicMethod類允許在執行時生成和執行方法,而無需建立動態程式集或動態型別來容納該方法。
    • 由即時(JIT)編譯器生成的可執行程式碼在DynamicMethod物件被回收時也會被回收。
    • 動態方法是生成和執行少量程式碼的最有效方式。
    • 如果需要動態建立一個或多個方法,應使用 DynamicMethod
  2. MethodBuilder:

    • MethodBuilder用於在動態程式集中建立方法。
    • 如果要建立整個型別(包括欄位、屬性、方法等),則需要先建立一個動態程式集(AssemblyBuilder),然後在其中建立一個模組(ModuleBuilder),最後再建立一個或多個型別(TypeBuilder)。
    • 若要在這些型別中建立方法,可以使用MethodBuilder

通俗的講人話即是:

1、如果是生成動態程式集,包括建立動態類,那麼使用 MethodBuilder。

2、如果只是定義動態方法供呼叫,使用 DynamicMethod,因為它不用定義整個程式集,直接起手就是方法。

3、使用委託呼叫方法:MethodBuilder 和 DynamicMethod 都支援,但 DynamicMethod 直接提供 CreateDelegate 方法,方便起手呼叫。

總結:

在本文的第五部分中,我們深入探討了 .NET Emit 中動態生成方法的兩種方式:MethodBuilder 和 DynamicMethod。

透過 MethodBuilder,我們可以在執行時動態建立和定義方法,為其新增引數、自定義屬性等後設資料資訊。

而 DynamicMethod 則提供了一種更靈活的動態方法生成方式,特別適合於需要高效能的動態程式碼生成場景。

在本篇教程中,我們學習瞭如何使用 MethodBuilder 來建立動態方法,並且演示瞭如何定義帶有引用引數的動態方法以及如何向動態方法新增自定義屬性。

這些內容對於理解動態方法的建立和擴充套件具有重要意義。

在下一篇中,我們將重點講述IL語言,IL(Intermediate Language)是.NET平臺上的一種中間語言,它是由C#、VB.NET等高階語言編譯成的一種低階語言表示形式。

我們將會詳細介紹IL語言的基本結構、指令集、堆疊操作等內容,幫助讀者更深入地理解.NET動態方法的內部實現和執行過程。

透過深入瞭解IL語言,讀者將能夠更好地掌握.NET平臺上動態程式碼生成的技術,並且能夠對IL程式碼進行最佳化和除錯,從而更好地應用於實際的軟體開發專案中。

敬請期待下一篇教程的釋出,讓我們一起探究IL語言的奧秘吧!

相關文章