KDecker
KDecker

Reputation: 7128

Referencing various Action<...> variable types with a single type?

In my application I have the need to reference various types of Action

Action action1;
Action<int> action2; 

Action actRef = action2; // Error: actRef is not the same type as action2

Basically I would like a type with which I can reference both action1 and action2. Is this possible?


The best solution I can think of is to wrap the Action<...> in another class that implements some interface.

public interface MyInterfaceType
{

}

public class MyActionWrapper : MyInterfaceType
{
    public Action MyAction { get; set; }
}

public class MyActionWrapper<T> : MyInterfaceType 
{
    public Action<T> MyAction { get; set; }
}

Then carry around references to MyInterfaceType.

public class MyActionsHolder
{
    List<MyInterfaceType> MyActions { get; set; }

    public void DoActions()
    {
        foreach(MyInterfaceType anAction in MyActions)
        {
            if(anAction is MyActionWrapper)
                ((MyActionWrapper)anAction).MyAction();
            else if(anAction is MyActionWrapper<int>)
                ((MyActionWrapper<int>)anAction).MyAction(1);
        }
    }
}

Is there a better solution? Or a way to clean this solution up?


More background on the specifics. I have written a simple scripting language which I am trying to parse into Action, each Action represents a parsed command from the script. As one commentor pointed out I can easily just wrap an Action with another

Action myParameterlessAction = new Action(() => MySingleParmAction(1));

though for a few of my commands I allow the user to supply a psuedo argument/variable. For example I might have an argument FOO that allows one argument. The argument is either a value for FOO or it is a "variable" meaning the current value of FOO when the command is executed.

FOO 231     // Set FOO = 231
FOO VAL_FOO // Set FOO to the desired value of FOO, whatever it may be at this time

So when I go to parse this second type of command I create an Action that takes a parameter so I can supply it when the time comes.

Action fooCommand1 = new Action(delegate() { MyForm.SetFooValue(231); });
Action<int> fooCommand2 = MyForm.SetFooValue;

I would like to be able to collect all Action create from parsing into a List<...> that I can then loop over and execute each action.. (I do realize that I will need a method to determine which Action need parameters supplied and how to determine what to supply them with; based on my solution above this information would be in MyInterfaceType most likely).


EDIT: In response to @The Anathema's answer, I have a command in my language that allows a user to wait for some period of time before continuing. The parsing of the script to the Action would look like this

public KeyValuePair<Delegate, object[]> CommandAction { get; set; }
...
Action commandDelegate = new Action(async delegate()
{
    await Task.Delay(msWait);
});
result.CommandAction = new KeyValuePair<Delegate, object[]>(commandDelegate, null);

Will Action.DynamicInvoke block until the Task.Delay(...) has completed? Where will the invocation happen, on the current thread or the thread on which the Action was created? Can I change either of these options?

Also per this SO answer I take it it would be worthwhile to try to resolve the Actions that are parameterless to Action myAct so that I can Invoke them instead of DynamicInvoke (Seeing that DynamicInvoke takes orders of magnitude longer to execute per the linked answer)?

Upvotes: 0

Views: 576

Answers (1)

Frozenthia
Frozenthia

Reputation: 759

You can reference them as the Delegate type.

        Delegate action1 = new Action(DoStuff);
        Delegate action2 = new Action<int>(DoStuff2);

You can even iterate over an IEnumerable<Delegate> and dynamically invoke each of them:

        var myActions = new List<Delegate>();
        myActions.Add(action1);
        myActions.Add(action2);

        foreach (var action in myActions)
        {
            action.DynamicInvoke();
        }

However, keep in mind that you will get an TargetParameterCountException if you're calling one without the appropriate args passed into the DynamicInvoke() method.

You can go a step further and have a dictionary that stores the arguments.

        Delegate action1 = new Action(DoStuff);
        Delegate action2 = new Action<int>(DoStuff2);

        var myActions = new Dictionary<Delegate, object[]>();
        myActions.Add(action1, null);
        myActions.Add(action2, new object[] { 0 });

        foreach (var action in myActions)
        {
            action.Key.DynamicInvoke(action.Value);
        }

If you need to call the same actions multiple times, change Dictionary<Delegate, object[]> to List<KeyValuePair<Delegate, object[]>>. This will allow you to have duplicate keys (actions).

Upvotes: 2

Related Questions