Динамическое выражение Linq с возвращаемым значением

Мне нужно создать динамическое выражение linq, и я начал работать со многими примерами. Я тестировал некоторые и некоторые работы, а некоторые нет. В этом случае я хочу создать метод, который выглядит так:

public bool Check(int intvar)
{
   if ( i > 2 )
     return true;
   else
     return false;
}

Теперь я написал следующее:

LabelTarget returnTarget = Expression.Label("label");
ParameterExpression para = Expression.Parameter(typeof(int), "intvalue");
Expression test = Expression.GreaterThan(para, Expression.Constant(5));
Expression iftrue = Expression.Return(returnTarget, Expression.Constant(true));
Expression iffalse = Expression.Return(returnTarget,                   Expression.Constant(false));
Expression.IfThenElse(test, iftrue, iffalse);

this.TheExpression = Expression.IfThenElse(test, iftrue, iffalse);
Expression.Lambda<Action<int>>(
this.TheExpression,
new ParameterExpression[] { para }
).Compile()(5);

Теперь он выбрасывает InvalidOperationException:

Невозможно перейти к метке "label"

Что не так? Мне нужно только возвращение true или false.

Ответ 1

Вам нужно изменить несколько вещей:

  • Поместите ярлык возврата в нижней части вашей функции в блочное выражение, как предложил Рене. Вот где ваш оператор return будет прыгать.

  • Объявите Лямбду как тип Func<int, bool>. Поскольку вы хотите получить возвращаемое значение, это должна быть функция, а не действие.

  • Объявите метку returnTarget как тип bool. Поскольку возвращаемое значение блочного выражения является значением его последнего оператора, метка должна иметь правильный тип.

  • Укажите значение по умолчанию для окончательной метки (= возвращаемое значение вашей функции, если метка достигнута с помощью обычного потока управления вместо оператора return).

    LabelTarget returnTarget = Expression.Label(typeof(bool));
    ParameterExpression para = Expression.Parameter(typeof(int), "intvalue");
    Expression test = Expression.GreaterThan(para, Expression.Constant(5));
    Expression iftrue = Expression.Return(returnTarget, Expression.Constant(true));
    Expression iffalse = Expression.Return(returnTarget, Expression.Constant(false));
    
    var ex = Expression.Block(
        Expression.IfThenElse(test, iftrue, iffalse),
        Expression.Label(returnTarget, Expression.Constant(false)));
    
    var compiled = Expression.Lambda<Func<int, bool>>(
        ex,
        new ParameterExpression[] { para }
    ).Compile();
    
    Console.WriteLine(compiled(5));     // prints "False"
    Console.WriteLine(compiled(6));     // prints "True"
    

Ответ 2

returnTarget в настоящее время ссылается только на ваш оператор if/then/else. Метка не помещается в оператор в любом месте. Поэтому он не знает, куда прыгать. Метка определяется только и ссылается, но не помещается.

Попробуйте использовать Expression.Block для объединения вашей лямбды и вашей метки.

Expression.Lambda<Action<int>>(
    Expression.Block(
        this.TheExpression,
        Expression.Label(returnTarget)
    ),
    new ParameterExpression[] { para }
    ).Compile()(5);

Не проверял, но это общее направление, в котором вы можете найти свой ответ.

-update- проверял его, лямбда выше компилируется и работает просто отлично, как она стоит сейчас.

-update2- apparantly, вы также хотите вернуть значение, позвольте мне взглянуть, по крайней мере, это должен быть Func, а не Action.

Ответ 3

Если у вас есть простое условие условия, как это:

if (condition)
    return expression1;
else
    return expression2;

Вы можете преобразовать это в троичное выражение: condition? expression1: expression2 condition? expression1: expression2. И тогда вы можете создать выражение, не используя Label, Return или Goto.

Expression condition;
Expression expression1;
Expression expression2;
/* ... */
Expression body = Expression.Condition(
    test:    condition,
    ifTrue:  expression1,
    ifFalse: expression2);