How can I dynamically create an Action<T> at runtime?
Asked Answered
C

6

18

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

Crucify answered 26/8, 2012 at 15:22 Comment(8)
Which part of action = new Action<int>(obj => Console.WriteLine("Called = " + obj)); do you want to generate dynamically?Judon
I want to create\initialise the parameter 'action'Crucify
I'm confused. There is no parameter called action. What do you want to achieve?Judon
As you can see in the answers, people does not understand the point. Could you may explain the context and what you want to achieve?Erda
simple how do you at run time create an instance of Action<T> when T is only know at runtime, you can not infer it staticallyCrucify
If you are on .NET 4, you could try Action<dynamic>Hertz
Why just Action<object> doesn't work for you?Zales
@Hertz I'm exploring your answer as it seems to simplify the reflexion workImperative
W
7

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();
    }
}
Willner answered 22/9, 2012 at 6:29 Comment(0)
C
5

Create a generic method to produce an Action with the desired generic parameter:

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

Call it like this:

var action = GetType()
            .GetMethod(nameof(CreateAction), BindingFlags.NonPublic | BindingFlags.Instance)
            ?.MakeGenericMethod(type)
            ?.Invoke(this, new object[]{});
Chairwoman answered 9/6, 2021 at 9:18 Comment(1)
best answer in my opinion!Merit
I
1

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.

Inappreciative answered 19/9, 2012 at 23:5 Comment(0)
I
0

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

Imperative answered 7/2, 2019 at 14:57 Comment(0)
J
-1

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.

Judon answered 9/9, 2012 at 12:34 Comment(2)
"No performance difference" To what? you haven't provided any code that actually dynamically creates a delegate. You conveniently provide an abstract class with an abstract method CreateActionImpl but don't provide an implementation--the only method that would actually dynamically create the action.And
@Peter I've explained the performance implications in the paragraph following the claim. Also, the OP wanted just the type parameter of the delegate late bound. Which the code does without Reflection.Emit.Judon
M
-2

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.

Messina answered 26/8, 2012 at 16:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.