Skip to content

Repeated generation of same interface (name) causes InvalidCastException with DispatchProxy.Create #84755

@cklutz

Description

@cklutz

Description

Consider the following scenario:

  1. Create an interface dynamically
  2. Use DispatchProxy.Create() to create a proxy (using some arbitrary DispatchProxy-derived class)
  3. Repeat with (1)

The first time 1 and 2 are run, everything works. The second time they are run, you get an InvalidCastException.

Reproduction Steps

using System;
using System.Reflection.Emit;
using System.Reflection;

for (int i = 0; i < 2; i++)
{
    var an = new AssemblyName("Test");
    var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
    var moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicDomain");
    var tb = moduleBuilder.DefineType("IDummy", TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract, null);
    var type = tb.CreateType();
    Console.WriteLine(type);

    var method = typeof(DispatchProxy).GetMethod(nameof(DispatchProxy.Create), BindingFlags.Static | BindingFlags.Public)!
        .MakeGenericMethod(new[] { type!, typeof(HelperProxy) });
    var proxy = method.Invoke(null, new object[] { });
    Console.WriteLine(proxy);
}

public class HelperProxy : DispatchProxy
{
    protected override object? Invoke(MethodInfo targetMethod, object[] args) => throw null!;
}

Expected behavior

Output:

IDummy
generatedProxy_1
IDummy
generatedProxy_2

Actual behavior

Output:

IDummy
generatedProxy_1
IDummy
Unhandled exception. System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.InvalidCastException: Unable to cast object of type 'generatedProxy_2' to type 'IDummy'.
   at System.Reflection.DispatchProxy.Create[T,TProxy]()
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at Program.<Main>$(String[] args) in C:\Sources\TestWcf\DispatchProxyFailure\Program.cs:line 15

Regression?

No response

Known Workarounds

Not regenerating the interface, but reusing the once created Type. However, this is not feasible in real world applications in all situations.

Another workaround is to make sure that name of the dynamically generated assembly is unique (e.g. in this example by adding the loop counter value).

Configuration

Reproduced with Release/Debug builds with .NET 6.0 and .NET 7.0.

Other information

It looks as if there is some caching going on. For example, if you generate the type once and repeatedly generate a proxy for that, the output is as follows:

IDummy
generatedProxy_1
IDummy
generatedProxy_1

So despite calling DispatchProxy.Create again, it returns the same type. Looking at the source there are some static dictionaries involved (in DispatchProxyGenerator).

It looks like that for the newly generated IDummy (loop n=1) it does recognize it is a different (interface) type (not taking the proxy from the dictionary.

Why the cast then fails here:

public static T Create<[DynamicallyAccessedMembers(T, TProxy>() where TProxy : DispatchProxy
{
	return (T)DispatchProxyGenerator.CreateProxyInstance(typeof(TProxy), typeof(T));
}

is beyond me. It almost looks as if T is not "the same" as typeof(T).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions