如何在运行时动态创建一个Action<T>?

18
我希望能在运行时执行以下等效操作:
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<int>(obj => Console.WriteLine("Called = " + obj)); 的哪一部分? - Mark
1
如何在运行时创建一个 Action<T> 的实例,当 T 只有在运行时才知道的情况下?你不能静态推断它。 - AwkwardCoder
1
如果您使用的是.NET 4,您可以尝试使用Action<dynamic>。 - prashanth
为什么只有Action<object>对你不起作用? - the_joric
@prashanth 我正在研究你的答案,因为它似乎简化了反射工作。 - StackHola
显示剩余3条评论
6个回答

7

如果您知道需要执行的操作以及如何执行它(就像您的示例中一样),为什么不创建一个通用方法来执行该操作,并以此方式创建委托呢?

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

5
创建一个通用方法来生成具有所需通用参数的 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[]{});

在我看来,最佳答案! - andrew.fox

1
您可以使用以下代码,如果类型可以转换为对象,则它可以正常工作:
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);

但是如果类型是枚举或其他值类型,它将无法工作。


0
感谢@prashanth的建议,我成功地使用动态关键字动态创建和调用了一个具有运行时类型的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*/)
                    }
                );


-1
使用下面的代码创建一个委托,该委托在类型参数中进行延迟绑定。另请参阅如何使用反射检查和实例化泛型类型
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 Ritchie
@Peter,我已经在声称后的段落中解释了性能影响。此外,OP只想要委托后期绑定的类型参数。这段代码可以在不使用Reflection.Emit的情况下实现。 - Mark

-2

简短的回答是创建一个委托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传递两个参数;这是因为委托参数实际上编译为两个参数:函数的目标对象和函数指针。我不能为那里的花样动作而获得荣誉;从这个优秀答案借用了信息,教你如何使用反射传递委托参数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接