打开泛型接口方法的委托

本文关键字:方法 泛型接口 | 更新日期: 2023-09-27 18:06:19

我试图为通用接口方法创建一个开放实例委托,但我一直收到NotSupportedException。下面是不能运行的简化代码:

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

最后一行抛出NotSupportedException, "指定的方法不受支持"。相比之下,非泛型开放实例委托运行良好:

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

和一个封闭的泛型委托也可以:

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

因此,封闭泛型委托和打开实例委托的配方是分开工作的,而不是合并工作的。它开始看起来像是一个运行时错误,或者故意遗漏。有人有什么见解吗?

打开泛型接口方法的委托

对于那些发现这个问题的人来说,这是一个主题和这个特定问题的概述(因为看起来OP已经在Microsoft Connect上得到了他的答案)。


为泛型接口方法创建开放实例泛型委托是不可能的(正如微软在这里确认的那样)。目前,可以实现以下任意一种开放实例/封闭静态、泛型/非泛型、接口/类方法的组合(在答案末尾提供了代码示例):

  • 为非泛型接口方法打开实例非泛型委托
  • 泛型接口方法的封闭静态泛型委托
  • 非泛型接口方法的封闭静态非泛型委托
  • 为泛型类方法打开实例泛型委托
  • 为非泛型类方法打开实例非泛型委托
  • 泛型类方法的封闭静态泛型委托
  • 非泛型类方法的封闭静态非泛型委托

通常,对泛型接口方法使用开放实例泛型委托的最佳替代方法是为泛型方法使用开放实例泛型委托。


代码示例

  • 为非泛型接口方法打开实例非泛型委托

    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);
    }
    
  • 泛型接口方法的封闭静态泛型委托

      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);
      }
    
  • 非泛型接口方法的封闭静态非泛型委托

      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);
      }
    
  • 为泛型类方法打开实例泛型委托

    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);
    }
    
  • 为非泛型类方法打开实例非泛型委托

    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);
    }
    
  • 泛型类方法的封闭静态泛型委托

    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);
    }
    
  • 非泛型类方法的封闭静态非泛型委托

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

如果你真的需要这个并且不介意在这个问题上投入太多的基础设施,你可以使用ldvirtftncalli

这对我来说似乎很奇怪,因为我认为这就是委托在幕后所做的基本上是做以下事情…

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

Ldvirtftn查找以找出要为此特定方法调用的函数指针。如果使用非虚拟泛型方法,其性能与绑定到同一函数的委托大致相同。如果它是一个虚拟泛型方法,它的速度大约是原来的两倍,也就是说它仍然有效,这是一个相当大的改进。
我用反射创造了这个。Emit,它似乎工作得很好,它可以调用一个封闭的虚拟泛型方法。不幸的是,与委托不同,此类型绑定到特定的方法。然而,令人头疼的是,运行时不允许创建使用ldvirtftnldftncalli操作码的动态方法。

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

如果你对代码生成没问题,你可以使用表达式树或动态方法来调用方法。它比直接委托慢一点但我们说的是很小的开销。

微软已经回答说,CLR无法做到这一点是一个已知的问题,但它无法在当前版本的。net中解决。我在这里解释了为什么这是不可能的,这一点仍然不清楚。由于某些原因,Open委托不能重用CLR中其他地方使用的调度逻辑,这对我来说似乎很奇怪。