Classe System.Reflection.Emit.AssemblyBuilder

Este artigo fornece observações complementares à documentação de referência para essa API.

Um assembly dinâmico é um assembly criado usando as APIs de emissão de reflexão. Um assembly dinâmico pode fazer referência a tipos definidos em outro assembly dinâmico ou estático. Você pode usar AssemblyBuilder para gerar assemblies dinâmicos na memória e executar seu código durante a execução do mesmo aplicativo. No .NET 9, adicionamos uma nova PersistedAssemblyBuilder implementação totalmente gerenciada de emissão de reflexão que permite salvar o assembly em um arquivo. No .NET Framework, você pode fazer as duas coisas — executar o assembly dinâmico e salvá-lo em um arquivo. O assembly dinâmico criado para salvar é chamado de assembly persistente , enquanto o assembly regular somente memória é chamado de transitório ou executável. No .NET Framework, um assembly dinâmico pode consistir em um ou mais módulos dinâmicos. No .NET Core e no .NET 5+, um assembly dinâmico só pode consistir em um módulo dinâmico.

A maneira como você cria uma AssemblyBuilder instância difere para cada implementação, mas as etapas adicionais para definir um módulo, tipo, método ou enum e para escrever IL são bastante semelhantes.

Assemblies dinâmicos executáveis no .NET

Para obter um objeto executável AssemblyBuilder , use o AssemblyBuilder.DefineDynamicAssembly método. Os assemblies dinâmicos podem ser criados usando um dos seguintes modos de acesso:

O modo de acesso deve ser especificado fornecendo o valor apropriado AssemblyBuilderAccess na chamada para o AssemblyBuilder.DefineDynamicAssembly método quando o assembly dinâmico é definido e não pode ser alterado posteriormente. O tempo de execução usa o modo de acesso de um assembly dinâmico para otimizar a representação interna do assembly.

O exemplo a seguir demonstra como criar e executar um assembly:

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 }));
}

Assemblies dinâmicos persistentes no .NET

A AssemblyBuilder.Save API não foi originalmente portada para o .NET (Core) porque a implementação dependia muito do código nativo específico do Windows que também não foi portado. No .NET 9, adicionamos uma implementação totalmente gerenciada Reflection.Emit que oferece suporte à salvamento. Essa implementação não tem dependência da implementação pré-existente, específica Reflection.Emit do tempo de execução. Ou seja, agora existem duas implementações diferentes no .NET, executáveis e persistentes. Para executar o assembly persistente, primeiro salve-o em um fluxo de memória ou em um arquivo e, em seguida, carregue-o de volta. Antes PersistedAssemblyBuilder, como você só podia executar um assembly gerado e não salvá-lo, era difícil depurar esses assemblies na memória. As vantagens de salvar um assembly dinâmico em um arquivo são:

  • Você pode verificar o assembly gerado com ferramentas como ILVerify ou descompilá-lo e examiná-lo manualmente com ferramentas como ILSpy.
  • O assembly salvo pode ser carregado diretamente, sem necessidade de compilar novamente, o que pode diminuir o tempo de inicialização do aplicativo.

Para criar uma PersistedAssemblyBuilder instância, use o public PersistedAssemblyBuilder(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null) construtor. O coreAssembly parâmetro é usado para resolver tipos de tempo de execução base e pode ser usado para resolver o controle de versão do assembly de referência:

  • Se Reflection.Emit for usado para gerar um assembly destinado a um TFM específico, abra os assemblies de referência para o TFM fornecido usando MetadataLoadContext e use o valor da propriedade MetadataLoadContext.CoreAssembly para coreAssembly. Esse valor permite que o gerador seja executado em uma versão de tempo de execução do .NET e direcione uma versão de tempo de execução diferente do .NET.

  • Se Reflection.Emit for usado para gerar um assembly que só será executado na mesma versão de tempo de execução que a versão de tempo de execução em que o compilador está sendo executado (normalmente no proc), o assembly principal pode ser typeof(object).Assembly. Os assemblies de referência não são necessários nesse caso.

O exemplo a seguir demonstra como criar e salvar um assembly em um fluxo e executá-lo:

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 }));
}

Para definir o ponto de entrada para um executável e/ou definir outras opções para o arquivo assembly, você pode chamar o public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData) método e usar os metadados preenchidos para gerar o assembly com as opções desejadas, por exemplo:

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); 

Observação

Os tokens de metadados para todos os membros são preenchidos Save na operação. Não use os tokens de um tipo gerado e seus membros antes de salvar, pois eles terão valores padrão ou lançarão exceções. É seguro usar tokens para tipos que são referenciados, não gerados.

Algumas APIs que não são importantes para emitir um assembly não são implementadas; por exemplo, GetCustomAttributes() não é implementado. Com a implementação de tempo de execução, você pôde usar essas APIs depois de criar o tipo. Para os persistentes AssemblyBuilder, eles jogam NotSupportedException ou NotImplementedException. Se você tiver um cenário que exija essas APIs, registre um problema no repositório dotnet/runtime.

Para obter uma maneira alternativa de gerar arquivos de assembly, consulte MetadataBuilder.

Assemblies dinâmicos persistentes no .NET Framework

No .NET Framework, assemblies dinâmicos e módulos podem ser salvos em arquivos. Para oferecer suporte a esse recurso, a AssemblyBuilderAccess enumeração declara dois campos adicionais: Save e RunAndSave.

Os módulos dinâmicos no assembly dinâmico persistente são salvos quando o assembly dinâmico é salvo usando o Save método. Para gerar um executável, o SetEntryPoint método deve ser chamado para identificar o método que é o ponto de entrada para o assembly. Os assemblies são salvos como DLLs por padrão, a menos que o SetEntryPoint método solicite a geração de um aplicativo de console ou um aplicativo baseado no Windows.

O exemplo a seguir demonstra como criar, salvar e executar um assembly usando o .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");
}

Alguns métodos na classe base Assembly , como GetModules e GetLoadedModules, não funcionarão corretamente quando chamados a partir de AssemblyBuilder objetos. Você pode carregar o assembly dinâmico definido e chamar os métodos no assembly carregado. Por exemplo, para garantir que os módulos de recursos sejam incluídos na lista de módulos retornados, chame GetModules o objeto carregado Assembly . Se um assembly dinâmico contiver mais de um módulo dinâmico, o nome do arquivo de manifesto do assembly deverá corresponder ao nome do módulo especificado como o primeiro argumento do DefineDynamicModule método.

A assinatura de um assembly dinâmico usando KeyPair não é eficaz até que o assembly seja salvo em disco. Assim, nomes fortes não funcionarão com montagens dinâmicas transitórias.

Os assemblies dinâmicos podem fazer referência a tipos definidos em outro assembly. Um assembly dinâmico transitório pode referenciar com segurança tipos definidos em outro assembly dinâmico transitório, um assembly dinâmico persistente ou um assembly estático. No entanto, o Common Language Runtime não permite que um módulo dinâmico persistente faça referência a um tipo definido em um módulo dinâmico transitório. Isso ocorre porque quando o módulo dinâmico persistente é carregado após ser salvo no disco, o tempo de execução não pode resolver as referências a tipos definidos no módulo dinâmico transitório.

Restrições à emissão para domínios de aplicativos remotos

Alguns cenários exigem que um assembly dinâmico seja criado e executado em um domínio de aplicativo remoto. A emissão de reflexão não permite que um assembly dinâmico seja emitido diretamente para um domínio de aplicativo remoto. A solução é emitir o assembly dinâmico no domínio do aplicativo atual, salvar o assembly dinâmico emitido no disco e carregar o assembly dinâmico no domínio do aplicativo remoto. Os domínios de aplicativo e comunicação remota são suportados somente no .NET Framework.