26

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?

naasking
  • 2,514
  • 1
  • 27
  • 32

3 Answers3

7

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);
    }
    

Michael
  • 2,910
  • 3
  • 15
  • 26
  • I haven't received an answer, not even in the bug I submitted that you linked to. MS just forwarded my bug report to the appropriate team. I will update here with their response of course. Your enumeration of the various possibilities can be summarized as such: a delegate is always allowed, *unless* it's an open delegate to a generic interface method. I'm afraid this isn't documented anywhere that I could find, nor is there a reason given for this one exception. I'm still monitoring this stack overflow thread in case someone can shed some light. Also, your use of 'static' seems non-standard. – naasking Aug 29 '11 at 00:02
  • Oh, sorry, I understood that bug report was an answer, sorry. And my use of "closed static" delegate isn't my choice, it's a standard term. (Used in the MSDN [http://msdn.microsoft.com/en-us/library/system.delegate.aspx] and the link explaining delegates in your first post) – Michael Aug 29 '11 at 06:21
  • The above is not exactly true: open instance delegate is always allowed unless it is a delegate to a generic *virtual* method. – Eugene Strizhok Jun 09 '13 at 09:20
1

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.

Michael B
  • 7,512
  • 3
  • 31
  • 57
  • Yes, I had to resort to codegen to dispatch to the generic interface method because of this bug. You can see the case I had to handle here [1], lines 88-120. I could eliminate at least half of those lines if it weren't for this ommission from the CLR. [1]: http://sasa.hg.sourceforge.net/hgweb/sasa/sasa/file/6ceb4b81b5b7/Sasa.Dynamics/Type.cs – naasking Sep 21 '11 at 03:39
  • While it may not help much with the line bloat, expression trees tend to improve readability. If that's not an option, than alternatively use the Emitted library. It really makes reflection emit code less ugly. Unfortunately, it's not quite as fluid as I want. – Michael B Sep 21 '11 at 16:26
0

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.

naasking
  • 2,514
  • 1
  • 27
  • 32