28

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?

1
  • Unfortunately not possible :( You can do it via Delegate and using reflection, but I am sure that is not what you want. Commented Apr 3, 2014 at 10:05

6 Answers 6

24

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

Sign up to request clarification or add additional context in comments.

8 Comments

Does this actually work? Does the DynamicInvoke reflect the Array as comma-delimited parameters into the methods execution?
Yes @GoldBishop. DynamicInvoke does exactly that. In this fiddle I'm showing some examples. Check msdn doc DynamicInvoke for more information.
hmmmm...the mischief that can be had with such knowledge :)
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: dotnetfiddle.net/qVlt47 I'll update my awnser explaining it. Thanks for comment!
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! ;-)
|
18

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); 

Comments

17

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); 

Comments

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)); 

1 Comment

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<out TResult>(params object[] args);
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; } } 

Comments

-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); } ... 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.