简体   繁体   中英

How can I write a method that accepts a lambda expression where the number of parameters to the lambda expression is unknown?

I'm trying to write a method that accepts a lambda expression. The number of input arguments to this lambda expression will vary in my code. The number of input arguments will determine what happens in the method body.

Pseudo-code follows:

private void foo(Expression e)
{
   // I don't know what parameter type foo should accept -- please suggest!
   double a, b, c;

   int count = e.NumberOfArguments;

   double[] args;
   if(count == 1) args = new[]{a};
   else if(count == 2) args = new[]{ a, b };
   ... and so on...

   e.Invoke(args);
}

private void goo()
{
   // called as follows:
   foo(x => true);
   foo((x,y) => true);
   foo((x,y,z) => true);
}

I don't really know what object foo should accept as a parameter (I'm guessing probably an Expression?) and I'm also not sure how to obtain the number of parameters in the lambda expression as well as invoke it.

Additionally, it would be a bonus if e could be invoked by passing an array with a variable number of elements.

Additional context:

I have a time series object. When foo is invoked with N parameters, I wish to use the time series together with it's 1st, 2nd, 3rd ... (N-1)-th derivative to generate a result (eg. bool). There could be instances when I need to use only the time series itself -- in this case I would only supply a single parameter; there are other situations where I wish to generate a result using the time series, it's first derivative as well as it's second derivative -- in this case, I would supply 3 parameters. And so on...

I hope that makes more sense. I am also open to more elegant ways to implement this requirement.

Well, it compiles and runs, but I'm not sure I'd want it in my codebase:

using System;
using System.Linq.Expressions;
namespace ConsoleApplication1
{
    internal sealed class Program
    {
        private static void Main(string[] args)
        {
            goo();
            Console.ReadLine();
        }
        private static void foo(LambdaExpression e)
        {
            double a, b, c;
            a = 1.0;
            b = 1.0;
            c = 1.0;
            bool result = false;
            int count = e.Parameters.Count;
            if (count == 1)
            {
                result = (bool)e.Compile().DynamicInvoke(a);
            }
            else if (count == 2)
            {
                result = (bool)e.Compile().DynamicInvoke(a,b);
            }
            Console.WriteLine(result);
        }
        private static void goo()
        {
            foo((Expression<Func<double, bool>>) (x => true));
            foo((Expression<Func<double, double, bool>>) ((x, y) => true));
            foo((Expression<Func<double, double, double, bool>>) ((x, y, z) => true));
        }
    }
}

And I'm also not sure whether it'll be the solution to your actual problem. (Also, insert usual caveats about complete lack of error checking, etc)

Based on Damien's excelent answer, I'd refactor the code to make it a little more useful.

static void Main(string[] args)
{
    TestFoo();
}

private static Tuple<bool, Exception> Foo<T>(LambdaExpression expression, out T result, params object[] parameters)
{
    bool succesful = false;
    result=default(T);
    Exception invokeException = null;

    if (expression.Parameters.Count == parameters.Length)
    {
        try
        {
            result = (T)expression.Compile().DynamicInvoke(parameters);
            succesful = true;
        }
        catch (Exception e)
        {
            invokeException = e;
        }
    }

    return new Tuple<bool, Exception>(succesful, invokeException);
}

private static void TestFoo()
{
    bool result;
    double sum;

    var succesful = Foo((Expression<Func<bool>>)(() => true), out result);
    Debug.Assert(succesful.Item1);
    Debug.Assert(result);
    Debug.Assert(succesful.Item2 == null);

    succesful = Foo((Expression<Func<double, bool>>)(x => x == 1.0), out result, 1.0);
    Debug.Assert(succesful.Item1);
    Debug.Assert(result);
    Debug.Assert(succesful.Item2 == null);

    succesful = Foo((Expression<Func<double, double, double>>)((x, y) => x + y), out sum, 2.0, 3.0);
    Debug.Assert(succesful.Item1);
    Debug.Assert(sum == 5);
    Debug.Assert(succesful.Item2 == null);

    succesful = Foo((Expression<Func<double, double, double>>)((x, y) => x + y), out sum, 2.0, new object());
    Debug.Assert(!succesful.Item1);
    Debug.Assert(succesful.Item2 is ArgumentException);

}

This should work reasonably well.

I think to make this maintainable, it makes more sense to make foo overloaded. And at that point, you can easily introduce custom logic for handling the number of parameters, because you know exactly how many parameters you have.

// forward to main implementation
private void foo(Func<bool> e)
{
   foo((a, b, c) => e());
}

// forward to main implementation
private void foo(Func<double, bool> e)
{
   foo((a, b, c) => e(a));
}

// forward to main implementation
private void foo(Func<double, double, bool> e)
{
   foo((a, b, c) => e(a, b));
}

// the main implementation
private void foo(Func<double, double, double, bool> e)
{
   double a, b, c;
   ...
   e(a, b, c);
}

private void goo()
{
   foo(() => true);
   foo(x => true);
   foo((x,y) => true);
   foo((x,y,z) => true);
}

Note that I'm not using Expression types here since your question talks about invoking it directly (instead of eg passing it to a LINQ provider), but the same basic approach would work just as well for Expression<Func<bool>> , Expression<Func<double, bool>> , ... overloads.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM