Open delegate for generic interface method

2019-03-09 00:58发布

问题:

I'm trying to create an open instance delegate for a generic interface method, but I keep receiving a NotSupportedException. Here is the simplified code that won't run:

interface IFoo
{
    void Bar<T>(T j);
}
class Foo : IFoo
{
    public void Bar<T>(T j)
    {
    }
}
static void Main(string[] args)
{
    var bar = typeof(IFoo).GetMethod("Bar").MakeGenericMethod(typeof(int));
    var x = Delegate.CreateDelegate(typeof(Action<IFoo, int>), null, bar);
}

The last line throws NotSupportedException, "Specified method is not supported". By comparison, a non-generic open instance delegate runs fine:

interface IFoo
{
    void Bar(int j);
}
class Foo : IFoo
{
    public void Bar(int j)
    {
    }
}
static void Main(string[] args)
{
    var bar = typeof(IFoo).GetMethod("Bar");
    var x = Delegate.CreateDelegate(typeof(Action<IFoo, int>), null, bar);
}

And a closed generic delegate also works:

interface IFoo
{
    void Bar<T>(T j);
}
class Foo : IFoo
{
    public void Bar<T>(T j)
    {
    }
}
static void Main(string[] args)
{
    var bar = typeof(IFoo).GetMethod("Bar").MakeGenericMethod(typeof(int));
    var x = Delegate.CreateDelegate(typeof(Action<int>), new Foo(), bar);
}

So the recipe for closed generic delegates and open instance delegates work separately, but not when combined. It's starting to look like either a runtime bug, or intentional omission. Anyone have any insight here?

回答1:

This is a recap of the topic and this specific issue for those that find this question (since it seems the OP has already got his answer on Microsoft Connect).


Answer

Creating an open instance generic delegate for a generic interface method is impossible (As confirmed by Microsoft here). Currently, it is possible to implement any of the following combinations of open-instance/closed static, generic/non-generic, interface/class methods (with code samples provided at the end of the answer):

  • open instance non-generic delegate for a non-generic interface method
  • closed static generic delegate for a generic interface method
  • closed static non-generic delegate for a non-generic interface method
  • open instance generic delegate for a generic class method
  • open instance non-generic delegate for a non-generic class method
  • closed static generic delegate for a generic class method
  • closed static non-generic delegate for a non-generic class method

Usually, the best replacement for an open instance generic delegate for a generic interface method is an open instance generic delegate for a generic class method.


Code Samples

  • open instance non-generic delegate for a non-generic interface method

    interface IFoo
    {
      void Bar(int j);
    }
    
    class Foo : IFoo
    {
      public void Bar(int j)
      {
      }
    }
    
    static void Main(string[] args)
    {
      var bar = typeof(IFoo).GetMethod("Bar");
      var x = Delegate.CreateDelegate(typeof(Action<IFoo, int>), null, bar);
    }
    
  • closed static generic delegate for a generic interface method

      interface IFoo
      {
        void Bar<T>(T j);
      }
    
      class Foo : IFoo
      {
        public void Bar<T>(T j)
        {
        }
      }
    
      static void Main(string[] args)
      {
        var bar = typeof(IFoo).GetMethod("Bar").MakeGenericMethod(typeof(int));
        var x = Delegate.CreateDelegate(typeof(Action<int>), new Foo(), bar);
      }
    
  • closed static non-generic delegate for a non-generic interface method

      interface IFoo
      {
        void Bar(int j);
      }
    
      class Foo : IFoo
      {
        public void Bar(int j)
        {
        }
      }
    
      static void Main(string[] args)
      {
        var bar = typeof(IFoo).GetMethod("Bar");
        var x = Delegate.CreateDelegate(typeof(Action<int>), new Foo(), bar);
      }
    
  • open instance generic delegate for a generic class method

    class Foo
    {
        public void Bar<T>(T j)
        {
        }
    }
    
    static void Main(string[] args)
    {
        var bar = typeof(Foo).GetMethod("Bar").MakeGenericMethod(typeof(int));
        var x = Delegate.CreateDelegate(typeof(Action<Foo, int>), null, bar);
    }
    
  • open instance non-generic delegate for a non-generic class method

    class Foo
    {
        public void Bar(int j)
        {
        }
    }
    
    static void Main(string[] args)
    {
        var bar = typeof(Foo).GetMethod("Bar");
        var x = Delegate.CreateDelegate(typeof(Action<Foo, int>), null, bar);
    }
    
  • closed static generic delegate for a generic class method

    class Foo
    {
        public void Bar<T>(T j)
        {
        }
    }
    
    static void Main(string[] args)
    {
        var bar = typeof(Foo).GetMethod("Bar").MakeGenericMethod(typeof(int));
        var x = Delegate.CreateDelegate(typeof(Action<int>), new Foo(), bar);
    }
    
  • closed static non-generic delegate for a non-generic class method

    class Foo
    {
        public void Bar(int j)
        {
        }
    }
    
    static void Main(string[] args)
    {
        var bar = typeof(Foo).GetMethod("Bar");
        var x = Delegate.CreateDelegate(typeof(Action<int>), new Foo(), bar);
    }
    



回答2:

Microsoft has answered that it's a known problem that the CLR can't do this, but it can't be solved in the current version of .NET. It's still not at all clear why this isn't possible as I explain there. Open delegates must not reuse the dispatching logic used everywhere else in the CLR for some reason, which just seems bizarre to me.



回答3:

Unusually if you really need this and don't mind throwing too much infrastructure at the problem, you can use ldvirtftn and calli.

It seems very strange to me as that's what I thought was what a delegate did behind the scene basically do the following...

public class MyAction{
public virtual void Invoke(SomeClass @this)
{
    ldarg.1
    dup
    ldvirtftn SomeClass.GenericMethod<Int32>
    calli void *(argument)
    ret
}

Ldvirtftn does a look up to figure out the function pointer to be invoked for this particular method. If you use a non-virtual generic method the performance is about the same as a delegate bound to the same function. And if it is a virtual generic method its about twice as slow, that said its still works so that's quite an improvement.
I created this using reflection.emit and it seems to work just fine and it can invoke a closed virtual generic method. Unfortunately, unlike a delegate this type is bound to a specific method. However, quite a pain in the butt is that the runtime doesn't allow you to create a dynamic method that uses ldvirtftn,ldftn, or calli opcode.

    public class SomeType
    {
        public virtual void DoNothing<T>()
        {
            Console.WriteLine(typeof(T));
        }
    }

    public abstract class MyAction
    {
        public abstract void Invoke(SomeType type);
    }


    public static void Main(string[] args)
    {
        TypeBuilder builder = AppDomain.CurrentDomain
            .DefineDynamicAssembly(new AssemblyName(MethodBase.GetCurrentMethod().DeclaringType.Name),
                                   AssemblyBuilderAccess.RunAndCollect)
            .DefineDynamicModule("Module").DefineType("MyType",
                                                      TypeAttributes.AnsiClass | TypeAttributes.AutoClass | TypeAttributes.Class |
                                                      TypeAttributes.Public | TypeAttributes.Sealed,
                                                      typeof (MyAction));
        var ilgen = builder.DefineMethod("Invoke",
                                         MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Final |
                                         MethodAttributes.Virtual,
                                         CallingConventions.HasThis,
                                         typeof (void), new[] {typeof (SomeType)}).GetILGenerator();
        ilgen.Emit(OpCodes.Ldarg_1);
        ilgen.Emit(OpCodes.Dup);
        ilgen.Emit(OpCodes.Ldvirtftn, typeof (SomeType).GetMethod("DoNothing").MakeGenericMethod(typeof (int)));
        ilgen.Emit(OpCodes.Calli, SignatureHelper.GetMethodSigHelper(CallingConventions.HasThis, typeof (void)));
        ilgen.Emit(OpCodes.Ret);
        MyAction action = Activator.CreateInstance(builder.CreateType()) as MyAction;
        action.Invoke(new SomeType());
    }

If you are okay with code generation you can use expression trees or dynamicmethod to just invoke the method. It's a little slower than a direct delegate but we're talking a tiny overhead.