Func<> with unknown number of parameters

You can use Delegate with DynamicInvoke.

With that, you don't need to handle with object[] in f.

TResult Foo<TResult>(Delegate f, params object[] args)
{
    var result = f.DynamicInvoke(args);
    return (TResult)Convert.ChangeType(result, typeof(TResult));
}

Usage:

Func<string, int, bool, bool> f = (name, age, active) =>
{
    if (name == "Jon" && age == 40 && active)
    {
        return true;
    }
    return false;
}; 

Foo<bool>(f,"Jon", 40, true);

I created a fiddle showing some examples: https://dotnetfiddle.net/LdmOqo


Note:

If you want to use a method group, you need to use an explict casting to Func:

public static bool Method(string name, int age)
{
    ...
}
var method = (Func<string, int, bool>)Method;
Foo<bool>(method, "Jon", 40);

Fiddle: https://dotnetfiddle.net/3ZPLsY


You could try something similar to what I posted here: https://stackoverflow.com/a/47556051/4681344

It will allow for any number of arguments, and enforces their types.

public delegate T ParamsAction<T>(params object[] args);

TResult Foo<TResult>(ParamsAction<TResult> f)
{
    TResult result = f();
    return result;
}

to call it, simply......

Foo(args => MethodToCallback("Bar", 123));

This could become easy with lambda expressions:

TResult Foo<TResult>(Func<TResult> f)
{
    return f();
}

Then usage could be like:

var result = Foo<int>(() => method(arg1, arg2, arg3));

Where method can be arbitrary method returning int.

This way you can pass any number of any erguments directly through lambda.

To support asynchoronous code we can define:

Task<TResult> Foo<TResult>(Func<Task<TResult>> f)
{
    return f();
}

// or with cancellation token
Task<TResult> Foo<TResult>(Func<CancellationToken, Task<TResult>> f, CancellationToken cancellationToken)
{
    return f(cancellationToken);
}

and use it like:

var asyncResult = await Foo(async () => await asyncMethod(arg1, arg2, arg3));
// With cancellation token
var asyncResult = await Foo(
    async (ct) => await asyncMethod(arg1, arg2, arg3, ct), 
    cancellationToken);

That's not possible. At best, you could have a delegate that also takes a variable number of arguments, and then have the delegate parse the arguments

TResult Foo<TResult>(Func<object[], TResult> f, params object[] args)
{
    TResult result = f(args);
    return result;
}


Foo<int>(args =>
{
    var name = args[0] as string;
    var age = (int) args[1];

    //...

    return age;
}, arg1, arg2, arg3);