23

Consider the following pseudo code:

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

The function accepts Func<> with unknown number of generic parameters and a list of the corresponding arguments. Is it possible to write it in C#? How to define and call Foo? How do I pass args to f?

Michał Turczyn
  • 32,028
  • 14
  • 47
  • 69
user2341923
  • 4,537
  • 6
  • 30
  • 44
  • Unfortunately not possible :( You can do it via `Delegate` and using reflection, but I am sure that is not what you want. – leppie Apr 03 '14 at 10:05

6 Answers6

20

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

Renan Araújo
  • 3,533
  • 11
  • 39
  • 49
  • Does this actually work? Does the `DynamicInvoke` reflect the Array as comma-delimited parameters into the methods execution? – GoldBishop Mar 21 '16 at 15:52
  • Yes @GoldBishop. `DynamicInvoke` does exactly that. In [this fiddle](https://dotnetfiddle.net/LdmOqo) I'm showing some examples. Check msdn doc [DynamicInvoke](https://msdn.microsoft.com/library/system.delegate.dynamicinvoke(v=vs.110).aspx) for more information. – Renan Araújo Mar 21 '16 at 18:12
  • 2
    hmmmm...the mischief that can be had with such knowledge :) – GoldBishop Mar 23 '16 at 17:28
  • This works, which is cool. Unfortunately you can't directly pass a method that takes arbitrary arguments, you must first create a Func. So a method `bool F(string name, int age, bool active) {...}` cannot be passed directly to `Foo(F, "Jon",40,true)`. – yoyo Apr 12 '17 at 23:57
  • 1
    Nice point @yoyo. To use this function with method group you need to use explict casting. I created another fiddle showing how you can do this: https://dotnetfiddle.net/qVlt47 I'll update my awnser explaining it. Thanks for comment! – Renan Araújo Apr 13 '17 at 02:38
  • 1
    Thanks for the update, and the fiddle (dotNetFiddle is great!) I've been trying to find a way of handling arbitrary parameters without needing to state the parameter types at the call site, but I've decided to find another way to solve my problem. (BTW, `Method("yoyo", 22)` definitely returns false! ;-) – yoyo Apr 13 '17 at 16:13
  • @yoyo I'm glad to hear that. :) – Renan Araújo Apr 13 '17 at 20:53
  • With c# 7.3 you can define `R Foo(D f, params object[] args) where D:Delegate` (https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters#delegate-constraints) – Jeremy Lakeman May 11 '20 at 01:15
18

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);
dcastro
  • 66,540
  • 21
  • 145
  • 155
17

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);
Michał Turczyn
  • 32,028
  • 14
  • 47
  • 69
2

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));
DirtyNative
  • 2,553
  • 2
  • 33
  • 58
Bryan Clark
  • 109
  • 1
  • 4
  • I think it should be called `ParamsFunc<>` instead, but otherwise I agree. I would make it covariant ("`out`") in `T`. For example `public delegate TResult ParamsFunc(params object[] args);` – Jeppe Stig Nielsen Jun 19 '19 at 07:46
0

In some cases you may be able to get away with a trick like this:

public static class MyClass
{
    private static T CommonWorkMethod<T>(Func<T> wishMultipleArgsFunc)
    {
        // ... do common preparation
        T returnValue = wishMultipleArgsFunc();
        // ... do common cleanup
        return returnValue;
    }

    public static int DoCommonWorkNoParams() => CommonWorkMethod<int>(ProduceIntWithNoParams);
    public static long DoCommonWorkWithLong(long p1) => CommonWorkMethod<long>(() => ProcessOneLong(p1));
    public static string DoCommonWorkWith2Params(int p1, long p2) => CommonWorkMethod<string>(() => ConvertToCollatedString(p1, p2));

    private static int ProduceIntWithNoParams() { return 5; }
}
DDRider62
  • 753
  • 6
  • 17
-1

Although it is not really what asked, a simple workaround would be to define several Foo method with different number of type arguments. It is uncommon to have function with more than 6 parameters, so one could define the following method and get away with almost every use case, while staying type safe. Renan's solution could then be used for the remaining cases.

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

public TResult Foo<T1, TResult>(Func<T1, TResult> f, T1 t1)
{
    return f(t1);
}

public TResult Foo<T1, T2, TResult>(Func<T1, T2, TResult> f, T1 t1, T2 t2)
{
    return f(t1, t2);
}

...
Richard
  • 992
  • 1
  • 11
  • 27