How can I dynamically create an Action at runti

2019-03-19 04:41发布

问题:

I want to be able to do the equivalent to the following at runtime:

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

I know I need to get the correct type for the Action, but not sure how to get the final bit using Delegate.Create. Type represent T in the Action definition.

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

the point people seem to be missing is I'm trying to create an instance of Action where T can not be specified statically because it is being used from a class derived from Attribute - this means T could be anything and it can not be defines as a generic definition

Cheers

回答1:

If you know what the operation you need to perform is and how to perform it regardless of type (as in your example) why not just make a generic method that performs the operation and create your delegate that way?

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:

You can use following code, it works if type can be casted to an object:

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

However if type is enum or other value type it won't work.



回答3:

Thanks to "@prashanth" suggestion, I managed to dynamically create and call an Action<> with a runtime type thanks to the dynamic keyword :

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

Example with a basic action handler :

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

Use case :

/* 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:

The short answer is to create a delegate MyActionDelegate and then use:

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

Here's a working example using a generic class:

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

Use it like this, which outputs 123 to the console:

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

You'll notice I'm passing two parameters to Constructor.Invoke; that's because it turns out that a delegate argument actually compiles as two arguments: the target object of the function, and a pointer to the function. I can't take credit for the fancy footwork there; "borrowed" info from this excellent answer on how to pass a delegate argument using reflection.



回答5:

Use the following code to create a delegate, which is late bound in the type parameter. See also 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);

That said, I don't recommend using this method. Instead, use

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

It offers

  • The same functionality.

    Your original code "Called = " + obj" calls .ToString() on the parameter. So does the above.

  • No performance difference.

    If the obj parameter is a value type, both variants perform a boxing operation. The boxing in the first is not obvious, but "Called = " + obj" boxes value types.

  • Shorter and less error-prone.