Привести делегата к динамическому типу аргумента через динамически генерируемый код CIL? - PullRequest
0 голосов
/ 20 сентября 2019

Вопрос в некоторой степени связан с этим: Как я могу привести делегата, который получает аргумент производного типа, к делегату с аргументом базового типа? , но у меня динамическая ситуация.

Допустим, у меня есть два класса:

class Base
{ }

class Derived : Base
{ }

static class Workers
{
    public static void DoSomething(Derived obj) { ... }
}

Как видите, Workers.DoSomething - это Action<Derived>, и я хочу привести его к Action<Base>.Я знаю, что это небезопасно, но мой случай таков: я держу словарь

Dictionary<Type, Action<Base>> actions;

и на основе заданных объектов obj.GetType() извлекаю одно действие и вызываю его.И поэтому я гарантирую в своем коде, что такое действие будет вызываться с соответствующим типом.

Но эти действия, очевидно, зависят от производного типа.Теперь связанный вопрос предлагает что-то вроде

actions[typeof(Derived)] = (obj) => Workers.DoSomething((Derived)obj);

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

Type objType;  // given
MethodInfo doSomethingMethod;  // given, guaranteed to be Action<objType>
actions[objType] = // here what?

Пока что, к удивлению, самое простое решение, которое я придумал, - это создать метод динамически следующим образом:

Type objType;  // given
MethodInfo doSomethingMethod;  // given
var dynamicMethod = new DynamicMethod(
    $"Dynamic{doSomethingMethod.Name}",
    typeof(void),
    new Type[] { typeof(Base) },
    typeof(Base).Module
);
var il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Callvirt, doSomethingMethod, null);
il.Emit(OpCodes.Ret);
actions[objType] = (Action<Base>)dynamicMethod
    .CreateDelegate(typeof(Action<Base>));

И поэтому я принудительно вызываюУровень CIL.Мой настоящий код немного сложнее, так как эти действия принимают два параметра.Но это всего лишь шум.

Это работает (и в качестве бонуса нет приведения).Но это выглядит как ... Я не знаю, небезопасно.И, вероятно, трудно поддерживать.Есть ли лучший способ решить мою проблему?

Примечание. Я хочу избежать doSomethingMethod.Invoke из-за значительных накладных расходов.

Примечание 2: У меня нет контроля над этими классами и действиями.Я могу только осмотреть их.

Ответы [ 2 ]

1 голос
/ 20 сентября 2019

Вы, кажется, знаете, что вы переворачиваете правила ковариации и контравариантности вверх ногами, тем не менее, вот что-то довольно аккуратное, что может сработать для описываемой вами ситуации (вы также можете проверить это (b as Derived) != null просто для уверенности):

  class Base { }

  class Derived : Base { }

  static class Workers 
  { 
    public static void DoSomething(Derived obj) { Console.WriteLine("Test"); } 
  }

  class Program
  {
    static Dictionary<Type, Action<Base>> actions;

    //  *** Note use of dummy to avoid having to know T at compile time and T : Base constraint
    //  (compiler can't infer T from Action<T> alone, this way runtime works out T from given object instance)...
    static void AddAction<T>(T dummy, Action<T> a) where T : Base 
    {
      actions.Add(typeof(T), b => a(b as T));
    }

    static void Main(string[] args)
    {
      actions = new Dictionary<Type, Action<Base>>();
      var o = new Derived();  //  the object you get "from elsewhere"
      AddAction(o, Workers.DoSomething);
      actions[o.GetType()](o);
      Console.ReadKey();
    }
  }

Надеюсь, это полезно.(Очень любопытно по поводу "Почему?", Хотя; -)

0 голосов
/ 23 сентября 2019

Я не могу утверждать, что это моя идея, но нашел ее здесь: https://stackoverflow.com/a/32702091/1848953 во время исследования вашего вопроса.Во-первых:

private static Action<object> ConvertDelegateToAction<T>(Delegate d) { return obj => ((Action<T>)d)((T)obj); }
private static readonly MethodInfo CastMethodInfo = typeof(Program).GetMethod(nameof(ConvertDelegateToAction), BindingFlags.Static | BindingFlags.NonPublic);

используется:

public static Action<object> GetActionT(Type t, Delegate d) { return (Action<object>)CastMethodInfo.MakeGenericMethod(t).Invoke(null, new object[] { d }); }

Довольно аккуратно, я думаю.Вы GetType() во время выполнения и используете Invoke(), но только чтобы получить Action<object> заранее.(Вместо базового класса я использовал пустой интерфейс. Казалось, что он работает нормально.)

Дайте нам знать, если это приносит удовлетворение.

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