开放通用接口方法的委托

时间:2011-08-21 05:37:56

标签: c# generics delegates

我正在尝试为通用接口方法创建open instance delegate,但我一直收到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);
}

因此,关闭的通用委托和开放实例委托的方法单独工作,但在组合时则不然。它开始看起来像运行时错误或故意遗漏。有人有任何见解吗?

3 个答案:

答案 0 :(得分:5)

这是对主题的回顾以及针对那些发现此问题的人的特定问题(因为看起来OP已经在Microsoft Connect上获得了答案)。


答案

为通用接口方法创建开放实例通用委托是不可能的(由Microsoft here确认)。 目前,可以实现以下任何开放实例/闭合静态,通用/非泛型,接口/类方法的组合(在答案的最后提供代码示例):

  • 用于非通用接口方法的开放实例非泛型委托
  • 用于通用接口方法的闭合静态泛型委托
  • 关闭非通用接口方法的静态非泛型委托
  • 用于泛型类方法的开放实例泛型委托
  • 为非泛型类方法打开实例非泛型委托
  • 关闭泛型类方法的静态泛型委托
  • 关闭非泛型类方法的静态非泛型委托

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


代码示例

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

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

答案 1 :(得分:1)

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

这对我来说似乎很奇怪,因为我认为这是代表在幕后所做的事情,基本上做了以下......

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

Ldvirtftn查找了要为此特定方法调用的函数指针。如果使用非虚拟泛型方法,则性能与绑定到同一函数的委托大致相同。如果它是一个虚拟通用方法,它的速度大约是它的两倍,那说它仍然有效,因此这是一个很大的改进 我使用reflection.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());
    }

如果您对代码生成没有问题,可以使用表达式树或dynamicmethod来调用该方法。它比直接代表慢一点,但我们谈的是一个很小的开销。

答案 2 :(得分:0)

Microsoft has answered已知问题是CLR无法做到这一点,但在当前版本的.NET中无法解决。它仍然一点都不清楚为什么这是不可能的,因为我在那里解释。由于某些原因,开放代表不得重复使用CLR中其他地方使用的调度逻辑,这对我来说似乎很奇怪。