2

I am trying to create an extension method that takes a func with arguments. I want to support variable number of arguments (none, 1, 2, ...10)

I have something like this that works for exactly three arguments. How can I simplify it to support variable number of arguments without having to copy-and-paste for every permutation? Is it even possible?

(NOTE: my example is very bare-bones. My real implementation has a lot more logic, e.g. supporting 'Retry' logic that has counts, Thread.Sleep, trace logging, exception handling, etc.)

Thanks!

public static class UtilExtensions
{
    public static TResult Execute<T1, T2, T3, TResult>(Func<T1, T2, T3, TResult> function, T1 argument1, T2 argument2, T3 argument3))
    {
        // do other stuff ... like logging

        try
        {
           // call our 'action'
           TResult result = function(argument1, argument2, argument3);
           return result;
        }
        catch (Exception ex)
        {
           // do other stuff ... like logging, handle Retry logic, etc.
        }
    }
}

and it is invoked like this:

public string DoSomething(int arg1, string arg2, MyObject arg3)
{
    if (arg1 == 1)
        throw new Exception("I threw an exception");

    return "I ran successfully";
}
public string DoSomethingElse()
{
    return "blah blah blah";
}

public string DoSomethingMore(DateTime dt)
{
    return "hi mom";
}


[TestMethod]
public void Should_call_UtilsExtensions_Execute_method_successfully()
{
    int p1 = 0;
    string p2 = "Hello";
    MyObject p3 = new MyObject();

    string results = UtilExtensions.Execute<int, string, MyObject, string>(
        DoSomething, p1, p2, p3);

    // ??? So how would I use my UtilExtensions api to call
    // DoSomethingElse (no arguments)
    // DoSomethingMore (one argument)
    // I'm okay to create overloads of my Execute method
    // but I don't want to copy-and-paste the same code/logic in each method

    results.Should().Be("I ran successfully");
}
Raymond
  • 3,382
  • 5
  • 43
  • 67
  • https://msdn.microsoft.com/en-us/library/w5zay9db.aspx – Lee Taylor Feb 02 '16 at 21:03
  • If I understand correctly what you want is something similar to C++ variadic templates. Look at this answer: http://stackoverflow.com/questions/6844890/simulate-variadic-templates-in-c-sharp – Szabolcs Dézsi Feb 02 '16 at 21:04
  • @Lee Taylor. I don't think params will work... but would be happy if you can show me otherwise. – Raymond Feb 02 '16 at 21:12
  • 2
    I believe params will work if you give up your strong typednesss and make an extension method on System.Delegate instead – Jeff Feb 02 '16 at 21:15
  • @Raymond - please explain why you think `params` wouldn't work – dashnick Feb 02 '16 at 21:15
  • @dashnick - I guess I'm asking for example code that will work.... if I knew how to do it, I wouldn't have posted this question. – Raymond Feb 02 '16 at 21:19
  • There's no real *reason* to do this. Just have `Func` and `Action` and if the caller wants to call a method that has parameters let them write a lambda that closes over that method invocation with the parameters they want to use. There's no reason for this method to know or care that a method is being called that takes some number of arguments, or what those arguments are. – Servy Feb 02 '16 at 22:22

2 Answers2

2

I don't think you can do this and as far as I know this is exactly the reason the Func and Action types have numerous distinct declarations for different parameter numbers in .Net. See the left hand side of https://msdn.microsoft.com/en-us/library/018hxwa8(v=vs.110).aspx

You are asking something similar to the following except in this case the OP was asking about interfaces Can I have a variable number of generic parameters?

The problem is essentially the same fundamental limitation of generics.

You might be able to do something with reflection but I think you'd be better picking a sensible number of parameters to support and write overloads.

Params won't work because for params all types in the array are the same type. T1, T2 etc are different types for your use. You'd have to declare the params as Object[] which completely defeats the point of generics. Although perhaps you are willing to give up the type checking if you truly have an unknown number of parameters likely to be impractically high.

Community
  • 1
  • 1
Alan Macdonald
  • 1,872
  • 20
  • 36
  • Thanks, that's what I thought... perhaps it is time for a code generator. – Raymond Feb 02 '16 at 22:00
  • @Raymond yeah I have seen a code generator used for this exact purpose before. It feels dirty but might actually be the best solution. As long as you've considered the implications of the other possible solutions and decided this is best for this one case then I think it's OK. – Alan Macdonald Feb 02 '16 at 22:10
1

You can't do that. An alternative is to create an extension method that accepts one argument, but that argument can be made up of anonymous functions. So your calling code will have full control over how to use it. Not the cleanest, but at least a little cleaner than a code generator route:

public static class UtilExtensions
{
    public static TResult Execute<TResult>(this Func<TResult> function)
    {
        // do logging

        try
        {
            TResult result = function();
            return result;
        }
        catch (Exception ex)
        {
            // do other stuff ... like logging, handle Retry logic, etc.
        }

        // or throw - this right here could prove unpredictable and is verrrry dirty
        return default(TResult);
    }
}

Usage:

var param1 = 1;
var param2 = "string";

Func<bool> function = () => 
{
    // do stuff with param1 and param2
    return true;
}

var results = function.Execute();
Balah
  • 2,530
  • 2
  • 16
  • 24
  • I do really like this approach and is what I ended up using... however, I still had to copy-and-paste my code 4 times. one each for Action vs Func, and again for async method support. I may still consider a code-generated approach. – Raymond Feb 03 '16 at 19:19
  • @Raymond yeah I imagined you'd have to repeat the code essentially 4 times - can't think of a way around that. Although... that code would sit in the same place and would have little reason change afterwards. I'd be curious to know if a code-generated approach ends up being worth the time effort. – Balah Feb 03 '16 at 19:46