【问题标题】:Cast delegate to dynamic argument type via dynamically generated CIL code?通过动态生成的 CIL 代码将委托转换为动态参数类型?
【发布时间】:2019-09-20 12:48:27
【问题描述】:

问题与此有些相关:How can I cast a delegate that takes a derived-type argument to a delegate with a base-type argument? 但我有一个动态的情况。

假设我有两个类:

class Base
{ }

class Derived : Base
{ }

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

如您所见,Workers.DoSomethingAction<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:我无法控制这些类和操作。我只能检查它们。

【问题讨论】:

  • 看起来还不错。您可以将其隐藏在工厂后面,按两者缓存。不是不安全,而是有点高级

标签: c# casting delegates cil


【解决方案1】:

您似乎意识到您正在颠倒协变和逆变规则,但这里有一些相当整洁的东西可能适用于您描述的情况(您也可以检查 (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();
    }
  }

希望这是有用的。 (很好奇“为什么?”虽然;-)

【讨论】:

  • 那不行。这只是隐藏了由于类型推断而在编译时已知类型的事实。看看在调用 AddAction 之前将“o”转换为 Base 时会发生什么。这就是我的情况。我有 Base,我知道它是一些派生的(因为在我的情况下 Base 是抽象的),但我在编译时没有这些知识。
  • 我这样做的原因是:我有 Base 和 Derived,而且我有很多不同的访问者,它们只是具有作用于 Derived 类型的方法的类。在那个访问者中,我通常编写一个主进程(基本节点)函数,然后根据类型运行一个适当的函数。但是访客类的数量越来越多。所以我想至少自动化这个 Process(Base node) 功能。导致它有很多样板。
  • 还要注意 Base 和 Derived 不是固定的(可能有多个这样的继承树),我不能真正修改它们来制作一些简单的虚拟性。我写的应该是一个框架。
  • 我明白了。会再想一想。
【解决方案2】:

我不能声称这是我的想法,但在研究您的问题时在这里找到了它: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&lt;object&gt;。 (而不是基类,我使用了一个空接口。似乎工作正常。)

让我们知道这是否让我们满意。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多