【问题标题】:How can I dynamically create an Action<T> at runtime?如何在运行时动态创建 Action<T>?
【发布时间】:2012-08-26 15:22:10
【问题描述】:

我希望能够在运行时执行以下等效操作:

var action = new Action<ANYTHING AT RUNTIME>(obj => Console.WriteLine("Called = " + obj));

我知道我需要为 Action 获取正确的类型,但不确定如何使用 Delegate.Create 获取最终位。 Type 代表 Action 定义中的 T。

var actionType = typeof(Action<>).MakeGenericType(Type);
var constructor = actionType.GetConstructors()[0];
var @delegate = Delegate.CreateDelegate(actionType, <WHAT GOES HERE>);

人们似乎缺少的一点是我正在尝试创建一个 Action 的实例,其中 T 不能静态指定,因为它是从派生自 Attribute 的类中使用的 - 这意味着 T 可以是任何东西,它不能被定义为泛型定义

干杯

【问题讨论】:

  • 你想动态生成action = new Action&lt;int&gt;(obj =&gt; Console.WriteLine("Called = " + obj));的哪一部分?
  • 我要创建\初始化参数'action'
  • 我很困惑。没有名为action参数。你想达到什么目标?
  • 简单你如何在运行时创建一个Action的实例,当T只在运行时知道,你不能静态推断它
  • 如果你在 .NET 4 上,你可以试试 Action

标签: c# .net generics reflection delegates


【解决方案1】:

如果您知道需要执行的操作是什么以及如何执行它而不管类型如何(如您的示例中所示),为什么不创建一个执行操作的通用方法并以这种方式创建您的委托?

class Program
{
    public static void Perform<T>(T value)
    {
        Console.WriteLine("Called = " + value);
    }

    public static Delegate CreateAction(Type type)
    {
        var methodInfo = typeof (Program).GetMethod("Perform").MakeGenericMethod(type);
        var actionT = typeof (Action<>).MakeGenericType(type);
        return Delegate.CreateDelegate(actionT, methodInfo);
    }

    static void Main(string[] args)
    {
        CreateAction(typeof (int)).DynamicInvoke(5);
        Console.ReadLine();
    }
}

【讨论】:

    【解决方案2】:

    您可以使用以下代码,如果可以将类型转换为对象,则它可以工作:

    Action<object> func = o => Console.WriteLine("Called = " + o.GetType().Name);
    var actionType = typeof(Action<>).MakeGenericType(type);
    var constructor = actionType.GetConstructors()[0];
    var @delegate = Delegate.CreateDelegate(actionType, func.Method);
    

    但是如果 type 是 enum 或其他值类型,它将不起作用。

    【讨论】:

      【解决方案3】:

      感谢“@prashanth”的建议,感谢 dynamic 关键字,我设法动态创建和调用具有运行时类型的 Action:

              public Action<dynamic> GetDynamicAction(/* some params */)
              {
                  return oDyn =>
                  {
                      //here is the action code with the use of /* some params */
                  };
              }
      

      基本动作处理程序示例:

      public class ActionHandler
      {
           public ReturnType DoAction<T>(Action<T> t)
           {
               //whatever needed
           }
      }
      
      

      用例:

      /* some params */ = Any runtime type specific data (in my case I had a Type and a MethodInfo passed as parameters and that were called in the action)
      
      var genericMethod = actionHandler.GetType().GetMethod(nameof(ActionHandler.DoAction));
      var method = genericMethod.MakeGenericMethod(runtimeGenericType);
      
      var actionResult = (ReturnType) method.Invoke(actionHandler, new object[]
                          {
                              GetDynamicAction(/*some params*/)
                          }
                      );
      
      

      【讨论】:

        【解决方案4】:

        创建一个泛型方法以生成具有所需泛型参数的 Action:

        private Action<T> CreateAction<T>() => 
                     new Action<T>(obj => Console.WriteLine("Called = " + (object)obj)); 
        

        这样称呼它:

        var action = GetType()
                    .GetMethod(nameof(CreateAction), BindingFlags.NonPublic | BindingFlags.Instance)
                    ?.MakeGenericMethod(type)
                    ?.Invoke(this, new object[]{});
        

        【讨论】:

          【解决方案5】:

          使用以下代码创建一个委托,该委托在类型参数中后期绑定。另见How to: Examine and Instantiate Generic Types with Reflection

          abstract class ActionHelper {
          
              protected abstract Delegate CreateActionImpl();
          
              // A subclass with a static type parameter
              private class ActionHelper<T> : ActionHelper {
                  protected override Delegate CreateActionImpl() {
                      // create an Action<T> and downcast
                      return new Action<T>(obj => Console.WriteLine("Called = " + (object)obj));
                  }
              }
          
              public static Delegate CreateAction(Type type) {
                  // create the type-specific type of the helper
                  var helperType = typeof(ActionHelper<>).MakeGenericType(type);
                  // create an instance of the helper
                  // and upcast to base class
                  var helper = (ActionHelper)Activator.CreateInstance(helperType);
                  // call base method
                  return helper.CreateActionImpl();
              }
          }
          
          // Usage
          // Note: The "var" is always "Delegate"
          var @delegate = ActionHelper.CreateAction(anyTypeAtRuntime);
          

          也就是说,我不推荐使用这种方法。相反,使用

          Action<object> action = obj => Console.WriteLine("Called = " + obj);
          

          它提供

          • 相同的功能。

            您的原始代码"Called = " + obj" 在参数上调用.ToString()。以上也是。

          • 没有性能差异。

            如果obj 参数是值类型,则两个变体都执行装箱操作。第一个中的装箱并不明显,而是"Called = " + obj" 装箱值类型。

          • 更短且不易出错。

          【讨论】:

          • “没有性能差异” 对什么?您还没有提供任何实际动态创建委托的代码。您可以方便地提供带有抽象方法 CreateActionImpl 的抽象类,但不提供实现——唯一实际动态创建动作的方法。
          • @Peter 我已经在声明后面的段落中解释了性能影响。此外,OP 想要 只是委托后期绑定的类型参数。没有 Reflection.Emit 的代码。
          【解决方案6】:

          简短的回答是创建一个委托MyActionDelegate,然后使用:

          delegate void MyActionDelegate(T arg);
          Delegate @delegate = new MyActionDelegate((a) => Console.WriteLine(a));
          

          这是一个使用泛型类的工作示例:

          public class MyClass<T>
          {
              public delegate void ActionDelegate(T arg);
          
              public void RunGenericAction(T arg)
              {
                  var actionType = typeof(Action<>).MakeGenericType(typeof(T));
                  var constructor = actionType.GetConstructors()[0];
                  Delegate @delegate = new ActionDelegate((a) => { Console.WriteLine(arg); });
                  var inst = (Action<T>)constructor.Invoke(new object[] { 
                      @delegate.Target,  
                      @delegate.Method.MethodHandle.GetFunctionPointer() 
                  });
                  inst(arg);
              }
          }
          

          像这样使用它,将123 输出到控制台:

          var c = new MyClass<int>();
          c.RunGenericAction(123);
          

          你会注意到我将两个参数传递给Constructor.Invoke;那是因为事实证明委托参数实际上编译为两个参数:函数的目标对象和指向函数的指针。我不能因为那里花哨的步法而受到赞扬。来自this excellent answer on how to pass a delegate argument using reflection.的“借来”信息

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多