sgowd
sgowd

Reputation: 1044

How do I pass variable arguments to a function that calls specific functions based on the number of arguments?

This is a follow up question to How do I pass a function pointer delegate for a different class in c#

I have a class

public class ClassA
{
    public void Foo()
    {
        Console.WriteLine("Foo()");
    }
    public void Foo(int x, int y)
    {
        Console.WriteLine("Foo(" + x.ToString() + ", " + y.ToString()  + ")" );
    }
    public void Foo(int x, int y, int z)
    {
        Console.WriteLine("Foo(" + x.ToString() + ", " + y.ToString() + ", " + z.ToString() + ")" );
    }
}

In another method, I would like to call the class functions of classA like this:

ClassA obj = new ClassA();
TakesFun(obj.Foo);
TakesFun(obj.Foo, 1, 2);
TakesFun(obj.Foo, 1, 2, 3);

What should be the syntax of TakesFun? I would like to make it generic to take in many/any/none arguments and call the appropriate function based on the number of arguments?

Obviously below function syntax is wrong, but I am looking for one function similar to that.

public static void TakesFun<TParam>(Action<TParam[]> action, params TParam[] paramList)
{
    Console.WriteLine("TakesFun");
    action(paramList);
}

EDIT1: ClassA Foo functions were just an example. I would like to call more complicated functions which can take any kind of argument. Like for eg:

public void CleanList(List<int> l)
{
    l.Clear();
}

Upvotes: 1

Views: 234

Answers (1)

Eric Lippert
Eric Lippert

Reputation: 660493

In another method, I would like to call the class functions of classA like this: TakesFun(obj.Foo);

You go on to note that TakesFun is a single method that takes an Action, rather than a collection of overloaded methods.

You can't always get what you want, and you'll have to learn to live with the disappointment. C# does not support the feature you want.

You are converting obj.Foo from a method group form to a delegate form, and that requires that C# performs overload resolution by comparing the delegate in the formal parameter list of TakesFun with the collection of methods named Foo, and then choose the best one.

Since there is no "best one" at compile time, this will fail.

Find another way to solve your problem. Your question sounds very much like what we call an "XY problem". That is, you have some real problem, you have a crazy idea about how to solve it that will never work, and now you're asking a question about your crazy idea. Ask a question about the real problem. There's a better way to solve it than this, almost certainly.

Now, if we relax some of the constraints of your problem then we can do it. In particular, if we relax the constraint that there be only one TakesFun and that it be variadic, then we can do it. I note that TakesFun is in fact the function application operation:

static void Apply(Action action) => action();
static void Apply<A>(Action<A> action, A a) => action(a);
static void Apply<A, B>(Action<A, B> action, A a, B b) => action(a, b);
static void Apply<A, B, C>(Action<A, B, C> action, A a, B b, C c) => action(a, b, c);
... and so on, as many as you need

And now the magic of overload resolution solves your problem:

Apply(obj.Foo, 1, 2, 3); // Works fine

Overload resolution deduces that Apply means Apply<int, int, int>, that the Action is Action<int, int, int> and that the Foo meant is the one that takes three integers.

C# type inference is pretty good, but you have to use it within the bounds that we designed into the language.


UPDATE: Based on your comments it sounds like you are trying to re-invent the task parallel library. I would investigate the TPL to see if it already meets your needs.

In C# we represent the notion of a unit of work that will be performed in the future as Task; there is a huge library and built-in language support for composing workflows out of tasks, scheduling their continuations to various threads, and so on. Use this work rather than attempting to invent it yourself.

If for example you wanted to create unstarted tasks that represent future work you would do it like this:

static Task TaskFactory(Action action) => new Task(action);
static Task TaskFactory<A>(Action<A> action, A a) => new Task(()=>action(a));
static Task TaskFactory<A, B>(Action<A, B> action, A a, B b) => new Task(()=>action(a, b));

and so on. Now you can do:

Task t = TaskFactory(obj.Foo, 1, 2);

and get back a t that when started will execute the action. You can then say what you want to happen when the task is complete:

t.ContinueWith(task => Console.WriteLine("Task complete!"));

and then start it up:

t.Start();

There are mechanisms for creating tasks that are already started and run on worker threads. There are mechanisms for scheduling tasks or their completions to run on particular threads. And so on; this is a huge topic. Ask another question if you have questions about that.

Upvotes: 6

Related Questions