Я новичок в выражениях 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. Я не уверен, могу ли я сделать это с помощью выражений.
Как и в обычном коде С#, переменные в 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)
Expression
s параметры и переменные никогда не сопоставляются по имени, они сопоставляются по ссылке объекта ParameterExpression
.
Параметры x
и y
скомпилированы в вашей предыдущей лямбда и не передаются во второй вызов Expression.Lambda
, но в любом случае похоже, что вы этого не хотите, потому что тогда у вас будут выражения с методом, подобным method(double[], double, double)
. Вы, скорее всего, хотели бы определить дополнительные x2
и y2
как локальные, которые вы могли бы передать как параметры вашей первой скомпилированной лямбда.
Это не на 100% ясно для меня то, что вы пытаетесь выполнить, если вы уточните, я, возможно, смогу помочь дальше. Иногда это помогает мне написать функции, как я хочу, чтобы они вели себя, а затем преобразовать их в выражение из этого.