Skip to content

DispatchProxy.Create<IProxy, Base> uncastable to Base in custom load context #60468

@vuplea

Description

@vuplea

Description

We are loading our plugins and their dependencies in a custom load context, except for the .NET runtime assemblies which are used from the default context.

One of our dependencies is loaded in both Default and plugin context since it is used by both host and plugins.
This dependency is using (Base)DispatchProxy.Create<IProxy, Base> which succeeds in default context but fails in plugin context with InvalidCastException.

Reproduction Steps

public class Program : DispatchProxy
{
    static void Main()
    {
        Console.WriteLine(DispatchProxyCreate(typeof(Program)).GetType().IsAssignableTo(typeof(Program)));
        // True

        var typeofProgramInCustomContext = new AssemblyLoadContext("custom")
            .LoadFromAssemblyPath(typeof(Program).Assembly.Location)
            .GetType(typeof(Program).FullName);

        Console.WriteLine(DispatchProxyCreate(typeofProgramInCustomContext).GetType().IsAssignableTo(typeofProgramInCustomContext));
        // False
    }

    public static object DispatchProxyCreate(Type parentType) => typeof(Program)
        .GetMethod(nameof(DispatchProxyCreateGeneric))
        .MakeGenericMethod(parentType)
        .Invoke(null, null);

    public static object DispatchProxyCreateGeneric<T>() where T : DispatchProxy =>
        DispatchProxy.Create<IDisposable, T>();

    protected override object Invoke(MethodInfo targetMethod, object[] args) => null;
}

Quick investigation reveals that this is induced by the single static AssemblyBuilder used for all proxies, which binds the parent type to the default context.

var typeId = 0;
var moduleBuilder = AssemblyBuilder
    .DefineDynamicAssembly(new AssemblyName("assembly"), AssemblyBuilderAccess.Run)
    .DefineDynamicModule("module");

Console.WriteLine(CreateDerivedType(typeof(Program)).IsAssignableTo(typeof(Program)));
// True

var typeofProgramInCustomContext = new AssemblyLoadContext("custom")
    .LoadFromAssemblyPath(typeof(Program).Assembly.Location)
    .GetType(typeof(Program).FullName);

Console.WriteLine(CreateDerivedType(typeofProgramInCustomContext).IsAssignableTo(typeofProgramInCustomContext));
// False

Type CreateDerivedType(Type parentType) => moduleBuilder
    .DefineType($"type{typeId++}", TypeAttributes.Public, parentType)
    .CreateType();

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions