Подтвердить что ты не робот

Открыть делегат для метода общего интерфейса

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

Таким образом, рецепт для закрытых общих делегатов и открытых делегатов экземпляра работает отдельно, но не в сочетании. Это начинает выглядеть как ошибка времени выполнения или умышленное упущение. У кого-нибудь есть понимание?

4b9b3361

Ответ 1

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

Ответ 2

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


Ответ

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

  • открытый экземпляр не общего делегата для метода не общего интерфейса.
  • закрытый статический общий делегат для метода общего интерфейса
  • закрытый статический не общий делегат для метода не общего интерфейса.
  • открытый экземпляр общего делегата для метода общего класса
  • открытый экземпляр не общего делегата для метода не общего класса
  • закрытый статический общий делегат для метода общего класса
  • закрытый статический не общий делегат для метода не общего класса

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


Образцы кода

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

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

Ответ 3

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

Мне кажется очень странным, что то, что я думал, было тем, что сделал делегат за сценой, в основном делает следующее...

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

ldvirtftn выполняет поиск, чтобы выяснить, какой указатель функции вызывается для этого конкретного метода. Если вы используете не виртуальный общий метод, производительность примерно такая же, как делегат, связанный с одной и той же функцией. И если это виртуальный общий метод, то он примерно в два раза медленнее, что говорит о том, что он все еще работает, так что вполне улучшается.
Я создал это с помощью reflection.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());
    }

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