views:

160

answers:

2

I need a method that takes an Action (or a Func), but the Action has a mixed number of parameters. What is the most straight forward and compact way to implement these overloads:

public void Execute<T>(Action<T> action, T param) {
    // TODO: Implement something like:
    // Execute(action, param, null);
}

public void Execute<T1,T2>(Action<T1,T2> action, T1 param1, T2 param2) {
    // TODO: Implement something like:
    // Execute(action, param1, param2, null);
}

public void Execute<T1,T2,T3>(Action<T1,T2,T3> action, T1 param1, T2 param2, T3 param3) {
    DoStuff();
    action(param1, param2, param3)
    DoMoreStuff();
}

// OR any other type of higher order function-solution
public void Execute(Action action, params object[] parameters) { ... } // ???

The content of the methods are exactly the same, except for the execution of the action and its parameters.

If possible, don't use any C# 4.0-specific features to solve this.

+3  A: 

The content of the methods are exactly the same, except for the execution of the action and its parameters.

That screams for using a higher order function, but since you already parameterized all the bits that are changing (execution of the action and its parameters) you're already there. Sorry, looks like you'll have to implement these overloads manually.

Just chaining through using nulls won't work since the delegates passed don't match. What you could do is wrap the passed action/func inside a lambda to peel off additional arguments:

public void Execute(Action<T> action, T param) {
     Execute( (a, _) => action(a) , param, null);
}

public void Execute(Action<T1, T2> action, T1 param1, T2 param2) {
    Execute( (a, b, _) => action(a, b) , param1, param2,  null);
}

public void Execute(Action<T1, T2, T3> action, T1 param1, T2 param2, T3 param3) {
    DoStuff();
    action(param1, param2, param3)
    DoMoreStuff();
}
Johannes Rudolph
Please feel free to also provide an higher order function-solution.
Seb Nilsson
@Seb: Regarding higher order function: You're already there by having parameterized your action.
Johannes Rudolph
+2  A: 

This is actually the best approach (keeping in mind Johannes's point that you could've also used a higher order function), as it is the most type-friendly (delegates will automatically be matched with the correct number and types of arguments) and does not require any bothersome DynamicInvoke calls.

Your last method definition is problematic, however. An Action by its very nature does not accept any parameters, so it isn't going to play nice with a params object[] argument. If you want a final overload that accepts a variable number of arguments, I'd go with DynamicInvoke after all, just for this method call:

public void Execute(Delegate action, params object[] parameters)
{
    DoStuff();
    action.DynamicInvoke(parameters);
    DoMoreStuff();
}

But to expand on what Johannes was saying, I think he was basically getting at something like this:

public Action<T1, T2, T3> CreateAction<T1, T2, T3>(Action action)
{
    return (T1 x, T2 y, T3 z) => action();
}

public Action<T1, T2, T3> CreateAction<T1, T2, T3>(Action<T1> action, T1 arg)
{
    return (T1 x, T2 y, T3 z) => action(arg);
}

And so on -- in other words, what you've already done, but in a general context so that the code is reusable in other places.

Dan Tao