System.Reflection.Emit.AssemblyBuilder 類別

本文提供此 API 參考文件的補充備註。

動態元件是使用 反思 ion Emit API 所建立的元件。 動態元件可以參考另一個動態或靜態元件中定義的類型。 您可以使用 AssemblyBuilder 在記憶體中產生動態元件,並在相同的應用程式執行期間執行其程式代碼。 在 .NET 9 中,我們新增了 PersistedAssemblyBuilder 具有完全受控的反映發出實作,可讓您將元件儲存到檔案中。 在 .NET Framework 中,您可以執行動態元件,並將它儲存至檔案。 為了儲存而建立的動態元件稱為持續性元件,而一般記憶體專用元件稱為暫時性可執行元件 在 .NET Framework 中,動態元件可以包含一或多個動態模組。 在 .NET Core 和 .NET 5+中,動態元件只能包含一個動態模組。

您為每個實作建立 AssemblyBuilder 實例的方式不同,但定義模組、類型、方法或列舉以及撰寫 IL 的進一步步驟相當類似。

.NET 中可執行的動態元件

若要取得可執行 AssemblyBuilder 的物件,請使用 AssemblyBuilder.DefineDynamicAssembly 方法。 您可以使用下列其中一種存取模式來建立動態元件:

在定義動態元件且稍後無法變更時,必須在呼叫 AssemblyBuilder.DefineDynamicAssembly 方法時提供適當的AssemblyBuilderAccess值來指定存取模式。 運行時間會使用動態元件的存取模式,將元件的內部表示優化。

下列範例示範如何建立和執行元件:

public void CreateAndRunAssembly(string assemblyPath)
{
    AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("MyAssembly"), AssemblyBuilderAccess.Run);
    ModuleBuilder mob = ab.DefineDynamicModule("MyModule");
    TypeBuilder tb = mob.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
    MethodBuilder mb = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static,
                                                                   typeof(int), new Type[] {typeof(int), typeof(int)});
    ILGenerator il = mb.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    Type type = tb.CreateType();

    MethodInfo method = type.GetMethod("SumMethod");
    Console.WriteLine(method.Invoke(null, new object[] { 5, 10 }));
}

.NET 中保存的動態元件

AssemblyBuilder.Save API 原本不會移植到 .NET (Core),因為實作嚴重依賴也未移植的 Windows 特定機器碼。 在 .NET 9 中,我們新增了支持儲存的完全受控 Reflection.Emit 實作。 此實作不相依於預先存在的運行時間特定 Reflection.Emit 實作。 也就是說,現在 .NET 中有兩個不同的實作,可執行和保存。 若要執行保存的元件,請先將它儲存到記憶體數據流或檔案中,然後將它載入回去。 在 之前 PersistedAssemblyBuilder,因為您只能執行產生的元件,而無法儲存它,所以很難偵錯這些記憶體內部元件。 將動態元件儲存至檔案的優點如下:

  • 您可以使用 ILVerify 之類的工具來驗證產生的元件,或使用 ILSpy 之類的工具手動檢查它。
  • 您可以直接載入已儲存的元件,而不需要再次編譯,這可能會減少應用程式啟動時間。

若要建立 PersistedAssemblyBuilder 實例,請使用 建構函 public PersistedAssemblyBuilder(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null) 式。 coreAssembly參數可用來解析基底運行時間類型,並可用於解析參考元件版本設定:

  • 如果使用 Reflection.Emit 來產生以特定 TFM 為目標的元件,請使用 MetadataLoadContext 開啟指定 TFM 的參考元件,並使用 的 coreAssemblyMetadataLoadContext.CoreAssembly 屬性值。 這個值可讓產生器在一個 .NET 運行時間版本上執行,並以不同的 .NET 運行時間版本為目標。

  • 如果 Reflection.Emit 用來產生只會在執行時間版本上執行的元件,與編譯程式執行所在的執行時間版本相同,則核心元件可以是 typeof(object).Assembly。 在此情況下,不需要參考元件。

下列範例示範如何將元件建立並儲存至數據流並加以執行:

public void CreateSaveAndRunAssembly(string assemblyPath)
{
    PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly);
    ModuleBuilder mob = ab.DefineDynamicModule("MyModule");
    TypeBuilder tb = mob.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
    MethodBuilder meb = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static,
                                                                   typeof(int), new Type[] {typeof(int), typeof(int)});
    ILGenerator il = meb.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    tb.CreateType();

    using var stream = new MemoryStream();
    ab.Save(stream);  // or pass filename to save into a file
    stream.Seek(0, SeekOrigin.Begin);
    Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(stream);
    MethodInfo method = assembly.GetType("MyType").GetMethod("SumMethod");
    Console.WriteLine(method.Invoke(null, new object[] { 5, 10 }));
}

若要設定可執行文件的進入點和/或設定元件檔的其他選項,您可以呼叫 public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData) 方法,並使用填入的元數據來產生具有所需選項的元件,例如:

PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly);
TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
// ...
MethodBuilder entryPoint = tb.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static);
ILGenerator il2 = entryPoint.GetILGenerator();
// ...
il2.Emit(OpCodes.Ret);
tb.CreateType();

MetadataBuilder metadataBuilder = ab.GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData);
PEHeaderBuilder peHeaderBuilder = new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage);

ManagedPEBuilder peBuilder = new ManagedPEBuilder(
                header: peHeaderBuilder,
                metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
                ilStream: ilStream,
                mappedFieldData: fieldData,
                entryPoint: MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken));

BlobBuilder peBlob = new BlobBuilder();
peBuilder.Serialize(peBlob);

// in case saving to a file:
using var fileStream = new FileStream("MyAssembly.exe", FileMode.Create, FileAccess.Write);
peBlob.WriteContentTo(fileStream); 

注意

作業上會填入所有成員的 Save 元數據令牌。 儲存之前,請勿使用所產生型別及其成員的令牌,因為它們會有預設值或擲回例外狀況。 針對參考的類型使用令牌,而不是產生令牌是安全的。

不會實作對發出元件而言不重要的一些 API;例如, GetCustomAttributes() 未實作 。 使用運行時間實作,您可以在建立類型之後使用這些 API。 對於儲存的 AssemblyBuilder,它們會擲回 NotSupportedExceptionNotImplementedException。 如果您有需要這些 API 的案例,請在 dotnet/runtime 存放庫中提出問題

如需產生元件檔的替代方式,請參閱 MetadataBuilder

.NET Framework 中保存的動態元件

在 .NET Framework 中,動態元件和模組可以儲存至檔案。 為了支援這項功能, AssemblyBuilderAccess 列舉會宣告兩個額外的欄位: SaveRunAndSave

當動態元件使用 Save 方法儲存動態元件時,會儲存可保存動態元件中的動態模組。 若要產生可執行檔, SetEntryPoint 必須呼叫 方法來識別元件進入點的方法。 除非方法要求產生主控台應用程式或以Windows為基礎的應用程式,否則 SetEntryPoint 元件預設會儲存為 DLL。

下列範例示範如何使用 .NET Framework 建立、儲存和執行元件。

public void CreateRunAndSaveAssembly(string assemblyPath)
{
    AssemblyBuilder ab = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("MyAssembly"), AssemblyBuilderAccess.RunAndSave);
    ModuleBuilder mob = ab.DefineDynamicModule("MyAssembly.dll");
    TypeBuilder tb = mob.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
    MethodBuilder meb = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static,
                                                                   typeof(int), new Type[] {typeof(int), typeof(int)});
    ILGenerator il = meb.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    Type type = tb.CreateType();

    MethodInfo method = type.GetMethod("SumMethod");
    Console.WriteLine(method.Invoke(null, new object[] { 5, 10 }));
    ab.Save("MyAssembly.dll");
}

Assembly 類上的某些方法,例如 GetModulesGetLoadedModules,無法在從 AssemblyBuilder 物件呼叫時正確運作。 您可以載入定義的動態元件,並在載入的元件上呼叫 方法。 例如,若要確保資源模組包含在傳回的模組清單中,請在載入Assembly的物件上呼叫 GetModules 。 如果動態元件包含一個以上的動態模組,則元件的指令清單檔名稱應該符合指定為方法之第一個自變數 DefineDynamicModule 的模組名稱。

使用的動態元件 KeyPair 簽署在元件儲存到磁碟之前無效。 因此,強名稱不適用於暫時性動態元件。

動態元件可以參考另一個元件中定義的類型。 暫時性動態元件可以安全地參考另一個暫時性動態元件、可保存動態元件或靜態元件中定義的類型。 不過,Common Language Runtime 不允許持續性動態模組參考暫時性動態模組中定義的類型。 這是因為當保存的動態模組在儲存至磁碟之後載入時,運行時間無法解析暫時性動態模組中所定義的類型參考。

對遠端應用程式域發出的限制

某些案例需要在遠端應用程式域中建立和執行動態元件。 反思 ion 發出不允許將動態元件直接發出至遠端應用程式域。 解決方案是在目前應用程式域中發出動態元件、將發出的動態元件儲存至磁碟,然後將動態元件載入遠端應用程式域。 只有 .NET Framework 才支持遠端和應用程式域。