Открытый делегат для универсального метода интерфейса - PullRequest
23 голосов
/ 21 августа 2011

Я пытаюсь создать делегат открытого экземпляра для универсального метода интерфейса, но я продолжаю получать 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 ]

5 голосов
/ 28 августа 2011

Это резюме темы и этой конкретной проблемы для тех, кто находит этот вопрос (поскольку кажется, что ОП уже получил свой ответ на Microsoft Connect).


Ответ

Создание универсального делегата открытого экземпляра для универсального метода интерфейса невозможно (как подтверждено Microsoft здесь ).В настоящее время возможно реализовать любую из следующих комбинаций открытых / закрытых статических, универсальных / неуниверсальных методов интерфейса / класса (с примерами кода, приведенными в конце ответа):

  • открытый экземпляр неуниверсального делегата для неуниверсального метода интерфейса
  • закрытый статический универсальный делегат для универсального метода интерфейса
  • закрытый статический неуниверсальный делегат по неуниверсальному интерфейсу метода
  • универсальный делегат открытого экземпляра для метода универсального класса
  • неуниверсальный делегат открытого экземпляра для метода не универсального класса
  • закрытый статический универсальный делегат для метода универсального класса
  • закрытый статический неуниверсальный делегат для неуниверсального метода класса

Обычно наилучшей заменой универсального делегата с открытым экземпляром для универсального метода интерфейса является универсальный делегат с открытым экземпляром дляуниверсальный класс метод.


Примеры кода

  • open Экземпляр неуниверсального делегата для неуниверсального метода интерфейса

    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 голос
/ 19 сентября 2011

Необычно, если вам это действительно нужно и вы не возражаете бросить слишком много инфраструктуры на проблему, вы можете использовать ldvirtftn и calli.

Это кажется мне очень странным, поскольку я думал, что именно так и поступил делегат за кулисами:

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

Ldvirtftn просматривает указатель на функцию, которая будет вызываться для этого конкретного метода. Если вы используете не виртуальный универсальный метод, производительность будет примерно такой же, как у делегата, привязанного к той же функции. И если это виртуальный универсальный метод, он примерно вдвое медленнее, значит, он все еще работает, так что это значительное улучшение.
Я создал это с помощью mirror.emit, и он, кажется, работает нормально, и он может вызывать закрытый виртуальный универсальный метод. К сожалению, в отличие от делегата этот тип связан с конкретным методом. Однако довольно неприятно то, что среда выполнения не позволяет создавать динамический метод, который использует код операции ldvirtftn, ldftn или calli.

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

Если вы в порядке с генерацией кода, вы можете использовать деревья выражений или динамический метод, чтобы просто вызвать метод. Это немного медленнее, чем у прямого делегата, но мы говорим о крошечных накладных расходах.

0 голосов
/ 10 сентября 2011

Microsoft ответила , что это известная проблема, что CLR не может этого сделать, но она не может быть решена в текущей версии .NET. Все еще не совсем ясно , почему это невозможно, как я объясняю там. Открытые делегаты не должны повторно использовать логику диспетчеризации, используемую повсюду в CLR, по какой-то причине, что мне кажется странным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...