How to: Inspect assembly contents using MetadataLoadContext

The reflection API in .NET by default enables developers to inspect the contents of assemblies loaded into the main execution context. However, sometimes it isn't possible to load an assembly into the execution context, for example, because it was compiled for another platform or processor architecture, or it's a reference assembly. The System.Reflection.MetadataLoadContext API allows you to load and inspect such assemblies. Assemblies loaded into the MetadataLoadContext are treated only as metadata, that is, you can examine types in the assembly, but you can't execute any code contained in it. Unlike the main execution context, the MetadataLoadContext doesn't automatically load dependencies from the current directory; instead it uses the custom binding logic provided by the MetadataAssemblyResolver passed to it.

Prerequisites

To use MetadataLoadContext, install the System.Reflection.MetadataLoadContext NuGet package. It is supported on any .NET Standard 2.0-compliant target framework, for example, .NET Core 2.0 or .NET Framework 4.6.1.

Create MetadataAssemblyResolver for MetadataLoadContext

Creating the MetadataLoadContext requires providing the instance of the MetadataAssemblyResolver. The simplest way to provide one is to use the PathAssemblyResolver, which resolves assemblies from the given collection of assembly path strings. This collection, besides assemblies you want to inspect directly, should also include all needed dependencies. For example, to read the custom attribute located in an external assembly, you should include that assembly or an exception will be thrown. In most cases, you should include at least the core assembly, that is, the assembly containing built-in system types, such as System.Object. The following code shows how to create the PathAssemblyResolver using the collection consisting of the inspected assembly and the current runtime's core assembly:

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

If you need access to all BCL types, you can include all runtime assemblies in the collection. The following code shows how to create the PathAssemblyResolver using the collection consisting of the inspected assembly and all assemblies of the current runtime:

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

Create MetadataLoadContext

To create the MetadataLoadContext, invoke its constructor MetadataLoadContext(MetadataAssemblyResolver, String), passing the previously created MetadataAssemblyResolver as the first parameter and the core assembly name as the second parameter. You can omit the core assembly name, in which case the constructor will attempt to use default names: "mscorlib", "System.Runtime", or "netstandard".

After you've created the context, you can load assemblies into it using methods such as LoadFromAssemblyPath. You can use all reflection APIs on loaded assemblies except ones that involve code execution. The GetCustomAttributes method does involve the execution of constructors, so use the GetCustomAttributesData method instead when you need to examine custom attributes in the MetadataLoadContext.

The following code sample creates MetadataLoadContext, loads the assembly into it, and outputs assembly attributes into the 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}");
        }
    }
}

If you need to test types in MetadataLoadContext for equality or assignability, only use type objects loaded into that context. Mixing MetadataLoadContext types with runtime types is not supported. For example, consider a type testedType in MetadataLoadContext. If you need to test whether another type is assignable from it, don't use code like typeof(MyType).IsAssignableFrom(testedType). Use code like this instead:

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

Example

For a complete code example, see the Inspect assembly contents using MetadataLoadContext sample.

See also