Como inspecionar o conteúdo do assembly usando MetadataLoadContext

Por padrão, a API de reflexão no .NET permite que os desenvolvedores inspecionem o conteúdo dos assemblies carregados no contexto de execução principal. No entanto, às vezes não é possível carregar um assembly no contexto de execução, por exemplo, porque ele foi compilado para outra arquitetura de plataforma ou processador, ou é um assembly de referência. A API System.Reflection.MetadataLoadContext permite carregar e inspecionar esses assemblies. Os assemblies carregados no MetadataLoadContext são tratados apenas como metadados, ou seja, você pode examinar tipos no assembly, mas não pode executar nenhum código contido nele. Ao contrário do contexto de execução principal, o MetadataLoadContext não carrega automaticamente as dependências do diretório atual; em vez disso, usa a lógica de associação personalizada fornecida pelo MetadataAssemblyResolver passado para ele.

Pré-requisitos

Para usar o MetadataLoadContext, instale o pacote NuGet System.Reflection.MetadataLoadContext. Ele tem suporte em qualquer estrutura de destino compatível com o .NET Standard 2.0, por exemplo, .NET Core 2.0 ou .NET Framework 4.6.1.

Criar MetadataAssemblyResolver para MetadataLoadContext

Criar o MetadataLoadContext requer o fornecimento da instância do MetadataAssemblyResolver. A maneira mais simples de fornecer um é usar o PathAssemblyResolver, que resolve assemblies da coleção determinada de cadeias de caracteres de caminho de assembly. Essa coleção, além dos assemblies que você deseja inspecionar diretamente, também deve incluir todas as dependências necessárias. Por exemplo, para ler o atributo personalizado localizado em um assembly externo, você deve incluir esse assembly ou uma exceção será lançada. Na maioria dos casos, você deve incluir pelo menos o assembly principal, ou seja, o assembly que contém tipos de sistema internos, como System.Object. O código a seguir mostra como criar o PathAssemblyResolver usando a coleção que consiste no assembly inspecionado e no assembly principal do runtime atual:

var resolver = new PathAssemblyResolver(new string[] { "ExampleAssembly.dll", typeof(object).Assembly.Location });

Se você precisar de acesso a todos os tipos de BCL, poderá incluir todos os assemblies de runtime na coleção. O código a seguir mostra como criar o PathAssemblyResolver usando a coleção que consiste no assembly inspecionado e em todos os assemblies do runtime atual:

// Get the array of runtime assemblies.
string[] runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll");

// Create the list of assembly paths consisting of runtime assemblies and the inspected assembly.
var paths = new List<string>(runtimeAssemblies);
paths.Add("ExampleAssembly.dll");

// Create PathAssemblyResolver that can resolve assemblies using the created list.
var resolver = new PathAssemblyResolver(paths);

Criar MetadataLoadContext

Para criar o construtor MetadataLoadContext, invoque seu construtor MetadataLoadContext(MetadataAssemblyResolver, String), passando o MetadataAssemblyResolver anteriormente criado como o primeiro parâmetro e o nome do assembly principal como o segundo parâmetro. Você pode omitir o nome do assembly principal, caso em que o construtor tentará usar nomes padrão: "mscorlib", "System.Runtime" ou "netstandard".

Depois de criar o contexto, você pode carregar assemblies nele usando métodos como LoadFromAssemblyPath. Você pode usar todas as APIs de reflexão em assemblies carregados, exceto os que envolvem a execução de código. O método GetCustomAttributes envolve a execução de construtores, portanto, use o método GetCustomAttributesData quando precisar examinar atributos personalizados no MetadataLoadContext.

O exemplo de código a seguir cria MetadataLoadContext, carrega o assembly nele e gera atributos de assembly no console:

var mlc = new MetadataLoadContext(resolver);

using (mlc)
{
    // Load assembly into MetadataLoadContext.
    Assembly assembly = mlc.LoadFromAssemblyPath("ExampleAssembly.dll");
    AssemblyName name = assembly.GetName();

    // Print assembly attribute information.
    Console.WriteLine($"{name.Name} has following attributes: ");

    foreach (CustomAttributeData attr in assembly.GetCustomAttributesData())
    {
        try
        {
            Console.WriteLine(attr.AttributeType);
        }
        catch (FileNotFoundException ex)
        {
            // We are missing the required dependency assembly.
            Console.WriteLine($"Error while getting attribute type: {ex.Message}");
        }
    }
}

Se você precisar testar igualdade ou atribuibilidade em tipos no MetadataLoadContext, use apenas objetos de tipo carregados nesse contexto. Não há suporte para a combinação de tipos de MetadataLoadContext com tipos de runtime. Por exemplo, considere um tipo de testedType em MetadataLoadContext. Se você precisar testar se outro tipo é atribuível a partir dele, não use código como typeof(MyType).IsAssignableFrom(testedType). Em vez disso, use um código como este:

Assembly matchAssembly = mlc.LoadFromAssemblyPath(typeof(MyType).Assembly.Location);
Type matchType = assembly.GetType(typeof(MyType).FullName!)!;

if (matchType.IsAssignableFrom(testedType))
{
    Console.WriteLine($"{nameof(matchType)} is assignable from {nameof(testedType)}");
}

Exemplo

Para obter um exemplo de código completo, confira Inspecionar o assembly usando o exemplo MetadataLoadContext.

Confira também