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

时间:2016-05-12 15:21:22

标签: c# .net expression-trees

我正在建立一个给我带来一些令人头疼的规则引擎。当我尝试构建如下构造的表达式树时,问题出现了:

    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);
    }

它采用Criterion类定义的值列表:

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

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

感谢阅读。

1 个答案:

答案 0 :(得分:1)

您可以使用表达式调试视图来查看已构建的内容。对于您的表达式,它会显示此信息(在为您的param指定名称&#34; x&#34;并使用Foo属性为int的{​​{1}}类进行调用:

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表达式的东西。如果这是真的,那么你可以这样做:

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);
}