由于未定义的变量,表达式树编译失败?

Expression Tree compilation failing due to undefined variable?

我正在构建一个让我头疼的规则引擎。当我尝试构建如下构造的表达式树时出现问题:

    public Tuple<Expression, ParameterExpression> BuildExpression<T>(string propertyName, Enums.Operator ruleOperator,
        ParameterExpression parameterExpression, List<object> values)
    {
        ParameterExpression listExpression = Expression.Parameter(typeof(List<object>));
        ParameterExpression counterExpression = Expression.Parameter(typeof(int));
        ParameterExpression toExpression = Expression.Parameter(typeof(int));
        ParameterExpression arrayExpression = Expression.Parameter(typeof(object[]));
        ParameterExpression valueExpression = Expression.Parameter(typeof(object));
        ParameterExpression checkExpression = Expression.Parameter(typeof(T));
        ParameterExpression returnExpression = Expression.Parameter(typeof(bool));
        MemberExpression body = null;

        foreach (var member in propertyName.Split('.'))
        {
            body = MemberExpression.Property(parameterExpression, member);
        }

        Expression expression = body.Expression;
        var type = expression.Type;
        ParameterExpression propertyExpression = Expression.Parameter(type);
        ParameterExpression localPropertyExpression = Expression.Parameter(type);

        LabelTarget breakLabel = Expression.Label();
        PropertyInfo result = typeof(List<object>).GetProperty("Count");
        MethodInfo toArray = typeof(List<object>).GetMethod("ToArray");
        MethodInfo getGetMethod = result.GetGetMethod();
        ConstantExpression constantExpression = Expression.Constant(true);
        if (ruleOperator == Enums.Operator.NotFoundIn)
        {
            constantExpression = Expression.Constant(false);
        }

        Expression loop = Expression.Block(
            new ParameterExpression[] { toExpression, arrayExpression, valueExpression, counterExpression,
            returnExpression, propertyExpression, localPropertyExpression, listExpression },
            Expression.Assign(listExpression, Expression.Constant(values)),
            Expression.Assign(toExpression, Expression.Call(listExpression, getGetMethod)),
            Expression.Assign(arrayExpression, Expression.Call(listExpression, toArray)),
            Expression.Assign(propertyExpression, expression),
            Expression.Loop(
                Expression.IfThenElse(
                    Expression.LessThan(counterExpression, toExpression),
                    Expression.Block(
                        Expression.Assign(valueExpression, Expression.ArrayAccess(arrayExpression, counterExpression)),
                        Expression.Assign(localPropertyExpression, expression),
                        Expression.IfThen(
                            Expression.Equal(propertyExpression, localPropertyExpression),
                            Expression.Block(Expression.Assign(returnExpression, constantExpression),
                                Expression.Break(breakLabel))),
                        Expression.Assign(Expression.ArrayAccess(arrayExpression, counterExpression), checkExpression),
                        Expression.PostIncrementAssign(counterExpression)),
                    Expression.Break(breakLabel)
                    ), breakLabel
                ),
                Expression.And(returnExpression, constantExpression)
            );

        return new Tuple<Expression, ParameterExpression>(Expression.Block(loop), checkExpression);
    }

它采用标准定义的值列表 class:

public class Criterion
{
    public List<object> Values { get; set; }

    public string PropertyName { get; set; }

    public Enums.Operator Operator_ { get; set; }
}

然后通过以下方法编译:

public Func<T, bool>[] CombineRules<T>(Criterion[] criteria)
        {
            var list = new List<Func<T, bool>>();
            foreach (var criterion in criteria)
            {
                var expressionBuilder = new ExpressionBuilder();
                var param = Expression.Parameter(typeof(T));
                var expression =
                    expressionBuilder.BuildExpression<T>(criterion.PropertyName, criterion.Operator_, param, criterion.Values);
                var func = Expression.Lambda<Func<T, bool>>(
                    expression.Item1, expression.Item2).Compile();
                list.Add(func);
            }

            return list.ToArray();
        }

但是,编译失败并出现以下异常:

System.InvalidOperationException: variable '' of type 'SnippInteractive.Web.Common.Models.V2.LineItem' referenced from scope '', but it is not defined

如果有人有任何有用的建议,我将不胜感激。

感谢阅读。

您可以使用表达式调试视图来查看您构建的内容。对于您的表达式,它显示了这一点(在将名称 "x" 分配给您的 param 并使用具有 int 属性 [的简单 Foo class Bar):

.Block() {
    .Block(
        System.Int32 $var1,
        System.Object[] $var2,
        System.Object $var3,
        System.Int32 $var4,
        System.Boolean $var5,
        ConsoleApplication6.Foo $var6,
        ConsoleApplication6.Foo $var7,
        System.Collections.Generic.List`1[System.Object] $var8) {
        $var8 = .Constant<System.Collections.Generic.List`1[System.Object]>(System.Collections.Generic.List`1[System.Object]);
        $var1 = .Call $var8.get_Count();
        $var2 = .Call $var8.ToArray();
        $var6 = $x;
        .Loop  {
            .If ($var4 < $var1) {
                .Block() {
                    $var3 = $var2[$var4];
                    $var7 = $x;
                    .If ($var6 == $var7) {
                        .Block() {
                            $var5 = True;
                            .Break #Label1 { }
                        }
                    } .Else {
                        .Default(System.Void)
                    };
                    $var2[$var4] = $var9;
                    $var4++
                }
            } .Else {
                .Break #Label1 { }
            }
        }
        .LabelTarget #Label1:;
        $var5 & True
    }
}

如您所见,许多变量在未分配的情况下被使用,这导致了您遇到的异常。

一些注意事项:

  • 由于变量命名,代码很难理解
  • 您应该使用 Expression.Variable 来定义表达式变量
  • 最好为表达式指定名称parameters/variables 以提高调试视图输出的可读性
  • 代码绝对没有按预期执行

据我所知,您似乎正在尝试构建类似于 object.property in/not in values 表达式的内容。如果这是真的,您可以按照以下方法进行操作:

public Tuple<Expression, ParameterExpression> BuildExpression<T>(string propertyName, Enums.Operator ruleOperator, ParameterExpression target, List<object> values)
{
    var property = propertyName.Split('.').Aggregate((Expression)target, Expression.PropertyOrField);
    var propertyValue = Expression.Variable(property.Type, "propertyValue");
    var array = Expression.Variable(typeof(object[]), "array");
    var length = Expression.Variable(typeof(int), "length");
    var index = Expression.Variable(typeof(int), "index");
    var value = Expression.Variable(typeof(object), "value");
    var result = Expression.Variable(typeof(bool), "result");
    var endLoop = Expression.Label("endLoop");
    bool success = ruleOperator != Enums.Operator.NotFoundIn;
    Expression body = Expression.Block
    (
        new ParameterExpression[] { propertyValue, array, length, index, result },
        Expression.Assign(propertyValue, property),
        Expression.Assign(array, Expression.Call(Expression.Constant(values), "ToArray", Type.EmptyTypes)),
        Expression.Assign(length, Expression.ArrayLength(array)),
        Expression.Assign(index, Expression.Constant(0)),
        Expression.Assign(result, Expression.Constant(!success)),
        Expression.Loop
        (
            Expression.IfThenElse
            (
                Expression.LessThan(index, length),
                Expression.Block
                (
                    Expression.IfThen
                    (
                        Expression.Equal(propertyValue, Expression.Convert(Expression.ArrayIndex(array, index), property.Type)),
                        Expression.Block
                        (
                            Expression.Assign(result, Expression.Constant(success)),
                            Expression.Break(endLoop)
                        )
                    ),
                    Expression.PostIncrementAssign(index)
                ),
                Expression.Break(endLoop)
            ),
            endLoop
        ),
        result
    );
    return Tuple.Create(body, target);
}

输出这个:

.Block(
    System.Int32 $propertyValue,
    System.Object[] $array,
    System.Int32 $length,
    System.Int32 $index,
    System.Boolean $result) {
    $propertyValue = $x.Bar;
    $array = .Call .Constant<System.Collections.Generic.List`1[System.Object]>(System.Collections.Generic.List`1[System.Object]).ToArray();
    $length = $array.Length;
    $index = 0;
    $result = False;
    .Loop  {
        .If ($index < $length) {
            .Block() {
                .If ($propertyValue == (System.Int32)$array[$index]) {
                    .Block() {
                        $result = True;
                        .Break endLoop { }
                    }
                } .Else {
                    .Default(System.Void)
                };
                $index++
            }
        } .Else {
            .Break endLoop { }
        }
    }
    .LabelTarget endLoop:;
    $result
}