Как установить параметры в выражениях C #

1

Я новичок в выражениях API в С#. Скажем, у меня есть выражение двух двойных параметров x и y. Я хотел бы обернуть это выражение в другое, где я могу передать двойной массив вместо двух аргументов. Я пробовал следующее (я тестирую его в F #, следовательно, синтаксис)

let x = Expression.Parameter(typeof<double>,"x")
let y = Expression.Parameter(typeof<double>,"y")
let givenExpr = Expression.Add(x,y)

// I know that I can evaluate givenExpr as follows
let myLambda1 = Expression.Lambda<Func<double,double,double>>(givenExpr, [| x; y |]).Compile()
// and this works
myLambda1.Invoke(1.0,1.0)


// But if I instead want to pass x and y in an array and then "assign" them, this doesn't work
let inputArray = Expression.Parameter(typeof<double[]>,"inputs")
let result = Expression.Parameter(typeof<double>,"result")

let blockStatements = new List<Expression>()
blockStatements.Add( Expression.Assign(x,Expression.ArrayAccess(inputArray,Expression.Constant(0))) );
blockStatements.Add( Expression.Assign(y,Expression.ArrayAccess(inputArray,Expression.Constant(1))) );
blockStatements.Add( Expression.Assign(result, givenExpr) );

let block = Expression.Block( [| result |], blockStatements )

let arrayLambda = Expression.Lambda<Func<double[],double>>(block, [|inputArray|]).Compile()
// This blows up 
arrayLambda.Invoke( [|1.0; 1.0|]) 
// with 
// System.InvalidOperationException: variable 'x' of type 'System.Double' referenced from scope '', but it is not defined
//   at System.Linq.Expressions.Compiler.VariableBinder.Reference(ParameterExpression node, VariableStorageKind storage)
//   at System.Linq.Expressions.Compiler.VariableBinder.VisitParameter(ParameterExpression node) ....

Ясно, что я не могу привязывать значения к параметрам, но я не знаю, как это исправить. Любые подсказки/предложения были бы высоко оценены! благодаря

EDIT: В основном я хочу, чтобы обернуть данную функцию (например, Foo ниже) из n переменных, чтобы вместо обернутой функции был выбран один аргумент типа double []. Для простых методов я мог бы сделать это следующим образом (пример для 2 аргументов)

double Foo( double x, double y);

double WrappedFoo( double [] args ){
    double x = args[0];
    double y = args[1];
    return Foo( x, y );
}

Я думаю о выражении как о функциональном теле (это может быть неверно!). Поэтому вместо Foo (double x, double y) говорят, что мне присваивается входное выражение (пусть оно называется Foo_Expression), которое, как я знаю, имеет два параметра. Я хотел бы обернуть это выражение в другое (эквивалент WrappedFoo выше), чтобы новое выражение принимало один аргумент двойного массива, а затем распределяло эти аргументы для вызова Foo_Expression. Я не уверен, могу ли я сделать это с помощью выражений.

  • 1
    Это F #, а не C #?
  • 0
    Я использую F # только для тестирования. Я намерен реализовать в C #. Я должен был сделать название .NET, а не C #. Сожалею.
Показать ещё 1 комментарий
Теги:
linq-expressions

2 ответа

1
Лучший ответ

Как и в обычном коде С#, переменные в Expression должны быть объявлены. При перегрузке Block который вы используете, первый параметр перечисляет объявленные переменные. Итак, вы объявляете result, но не x или y.

Он работает в вашей первой версии, потому что x и y являются параметрами, а не локальными переменными.

Итак, исправление:

let block = Expression.Block( [| x; y; result |], blockStatements )

Это работает, но в основном случайно. Вы не говорите, что result - это значение, которое должно быть возвращено, фактическое возвращаемое значение имеет последнее выражение (Expression больше похоже на F #, чем на С# в этом отношении).

Таким образом, код с исправлением будет эквивалентен:

double WrappedFoo(double[] inputs)
{
    double x;
    double y;
    double result;

    x = inputs[0];
    y = inputs[1];
    return (result = Foo(x, y));
}

Чтобы получить код, похожий на тот, который вы описали, избавиться от переменной result и изменить последний оператор только на выражение, которое вы хотите вернуть:

blockStatements.Add(givenExpr)
  • 0
    Это сработало! Спасибо вам большое! Я считаю, что это несколько противоречит интуитивному пониманию того, что, хотя в приведенном выше обычном коде C # имена переменных, очевидно, не должны совпадать с именами параметров, версия выражений сопоставляет локальные переменные по имени с параметрами данногоExpr. В любом случае, очень рад, что это работает!
  • 0
    @ user3727133 Я не понимаю, что ты говоришь. В Expression s параметры и переменные никогда не сопоставляются по имени, они сопоставляются по ссылке объекта ParameterExpression .
Показать ещё 1 комментарий
0

Параметры x и y скомпилированы в вашей предыдущей лямбда и не передаются во второй вызов Expression.Lambda, но в любом случае похоже, что вы этого не хотите, потому что тогда у вас будут выражения с методом, подобным method(double[], double, double). Вы, скорее всего, хотели бы определить дополнительные x2 и y2 как локальные, которые вы могли бы передать как параметры вашей первой скомпилированной лямбда.

Это не на 100% ясно для меня то, что вы пытаетесь выполнить, если вы уточните, я, возможно, смогу помочь дальше. Иногда это помогает мне написать функции, как я хочу, чтобы они вели себя, а затем преобразовать их в выражение из этого.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню