Type.GetConstructor (with parameter types) fails for dynamically loaded type

Viewed 21

I have a program that loads plugins using AssemblyLoadContext.LoadFromAssemblyName using this approach.

The plugin assembly loads fine, and I can locate types that implement a required interface, and instantiate these types with their default constructor (using Activator.CreateInstance), but when I try to locate a constructor with a particular parameter type, GetConstructor returns null, even though the required constructor can be seen in the debugger.

GetConstructor returns null

Constructor is present

When I try the same thing with the same type but statically loaded, the call succeeds.

What is happening here?

Update:

I moved the ILogger parameter out of the constructor into one of the methods that's declared in the interface and now Assembly.GetTypes (via RuntimeModule.GetTypes) throws ReflectionTypeLoadException with "Method 'blah' in type 'blah' from assembly 'blah' does not have an implementation."

The method in question is the one with the ILogger parameter. It's as if the runtime doesn't recognize the ILogger type in the interface and the ILogger type in the runtime-loaded assembly as being the same, but I can't figure out why that should be.

If I remove the ILogger parameter it all works smoothly again.

1 Answers

I was led to an answer by this Nate McMaster article.

Basically the point of the AssemblyLoadContext is to isolate plugins from each other and from the program loading them. Therefore the were not sharing the same ILogger type even though in practice they were using the same version.

So you have to tell the loader not to isolate any libraries that you want to share. You can do this by returning null from the Load method override for the assemblies in question.

internal class PluginLoadContext : AssemblyLoadContext
{
    private AssemblyDependencyResolver Resolver { get; }
    private string[] SharedAssemblies { get; } =
    {
        "Microsoft.Extensions.Logging.Abstractions"
    };

    public PluginLoadContext(string pluginPath)
    {
        Resolver = new(pluginPath);
    }

    protected override Assembly? Load(AssemblyName assemblyName)
    {
        if (SharedAssemblies.Contains(assemblyName.Name))
        {
            // Use the default resolver
            return null;
        }
        
        string? assemblyPath = Resolver.ResolveAssemblyToPath(assemblyName);
        return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null;
    }

    protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
    {
        string? libraryPath = Resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
        return libraryPath != null ? LoadUnmanagedDllFromPath(libraryPath) : IntPtr.Zero;
    }
}
Related