dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.27k stars 4.73k forks source link

Attempts to create delegates from generic methods declared on interfaces throws `NotSupportedException` #100748

Open boxofyellow opened 7 months ago

boxofyellow commented 7 months ago

Description

If you have an interface declared as

public interface I1
{
    public void GenericFoo<T>();
}

And a delegate declared as

public delegate void InterfaceDelegate(I1 i1);

Attempting to create an instance of that delegate with this will throw a NotSupportedException

var genericMethod = typeof(I1).GetMethod(nameof(I1.GenericFoo))!;
var method = genericMethod.MakeGenericMethod(typeof(int));
//
// This is where the NotSupportedException is throne
//
method.CreateDelegate<InterfaceDelegate>(); 

However if you instead use a non-generic method (of corse MakeGenericMethod won't be needed) but it will be able to create an instance of the deligte. Additionally if you define a new a class with a generic method that has the same signature, and a delegate for that class's method you will be able to use similar code create an instance of that delegate.

Reproduction Steps

I tried this in dotnet 6.0.420 and 8.0.203

public interface I1
{
    public void GenericFoo<T>();
}

public delegate void InterfaceDelegate(I1 i1);

public class Program
{
    public static void Main()
    {
        var genericMethod = typeof(I1).GetMethod(nameof(I1.GenericFoo))!;
        var method = genericMethod.MakeGenericMethod(typeof(int));
        //
        // This is where the NotSupportedException is throne
        //
        method.CreateDelegate<InterfaceDelegate>(); 
    }
}

Expected behavior

The above program can be run without an exception.

Actual behavior

~/Repos/MinRepro > dotnet run
Unhandled exception. System.NotSupportedException: Specified method is not supported.
   at System.Delegate.BindToMethodInfo(Object target, IRuntimeMethodInfo method, RuntimeType methodType, DelegateBindingFlags flags)
   at System.Delegate.CreateDelegateInternal(RuntimeType rtType, RuntimeMethodInfo rtMethod, Object firstArgument, DelegateBindingFlags flags)
   at System.Reflection.RuntimeMethodInfo.CreateDelegateInternal(Type delegateType, Object firstArgument, DelegateBindingFlags bindingFlags)
   at System.Reflection.MethodInfo.CreateDelegate[T]()
   at Program.Main()
~/Repos/MinRepro >

Regression?

I have only tried 6.0 and 8.0 and the behavior seems to be the same in both.

Known Workarounds

One workaround I have found is to use a DynamicMethod to create a method to invoke the generic method. It seems to work for very simple cases. However implementing that for arbitrary method signatures might tricky.

Configuration

~/Repos/MinRepro > dotnet --info
.NET SDK:
 Version:           8.0.203
 Commit:            5e1ceea679
 Workload version:  8.0.200-manifests.4e94be9c

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  14.4
 OS Platform: Darwin
 RID:         osx-x64
 Base Path:   /usr/local/share/dotnet/sdk/8.0.203/

.NET workloads installed:
There are no installed workloads to display.

Host:
  Version:      8.0.3
  Architecture: x64
  Commit:       9f4b1f5d66

.NET SDKs installed:
  6.0.420 [/usr/local/share/dotnet/sdk]
  8.0.203 [/usr/local/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.28 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 8.0.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.28 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 8.0.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

Other architectures found:
  None

Environment variables:
  Not set

global.json file:
  Not found

Learn more:
  https://aka.ms/dotnet/info

Download .NET:
  https://aka.ms/dotnet/download

Other information

🤷

dotnet-policy-service[bot] commented 7 months ago

Tagging subscribers to this area: @dotnet/area-system-reflection See info in area-owners.md if you want to be subscribed.

boxofyellow commented 7 months ago

I created this that shows some of the more odd behavior I have been seeing

https://github.com/boxofyellow/DelegateForGenericMethodExample

MichalPetryka commented 7 months ago

The exception is probably coming from here: https://github.com/dotnet/runtime/blob/9b57a265c7efd3732b035bade005561a04767128/src/coreclr/vm/comdelegate.cpp#L870-L874

boxofyellow commented 7 months ago

I think you are right @MichalPetryka, I checked in my the demo app and it looks like this problems effects all virtual generic methods.

MichalPetryka commented 7 months ago

@jkotas are GVM delegates supposed to not be implemented in CoreCLR?

hez2010 commented 7 months ago

Worth to note that this doesn't repro on NativeAOT.

jkotas commented 7 months ago

@jkotas are GVM delegates supposed to not be implemented in CoreCLR?

My guess is that it was not done because it is non-trivial to implement.

If we were to implement it, we should discuss the implementation approach first.