首页 文章

使用表达式树的Foreach循环

提问于
浏览
5

我已经看到了这个Issue while building dynamic Expression TreeExpression/Statement trees,因为我是表达树的新手,我仍然在努力去理解如何实现我想要的东西 .

一个人为的目标如下

public class TestObject
    {
        public TestObject()
        {
            ClassList = new List<Class>();
        }
        public int Age { get; set; }
        public List<Class> ClassList { get; set; } 
    }

    public class Class
    {
        public string Name { get; set; }
        public int ClassId { get; set; }
    }

在运行时,我遍历每个属性并生成一个Delegate,它转换为该属性的字符串 . 我已经完成了所有工作 . 我现在要处理的问题是,对于List类型,我需要能够对ClassList属性中的每个项应用一组动作,所以我需要一个foreach来允许我这样做 .

我现在有这个

//type==TestObject at runtime
//propertyName == "ClassList"
   ParameterExpression recordExpression = Expression.Parameter(type, "record");

   memberExpression = MemberExpression.Property(recordExpression, propertyName);

   Type getEnumerableDelegateType =
                typeof(Func<,>).MakeGenericType(new Type[] { type, memberExpression.Type}); 

   var getList = Expression.Lambda(getEnumerableDelegateType, memberExpression, recordExpression);

编译和调用时,GetList按预期返回List . 我正在努力的是如何创建一个表达式,该表达式将使用lambda表达式的结果并使用我已经为每个Class项创建的一组动作迭代它 .

最终我正在寻找一个lambda签名来匹配下面的 overallAction 签名

var getListFunc = new Func<TestObject, List<Class>>((TestObject obj1) => obj1.ClassList);

   Action<List<Class>> listAction = delegate(List<Class> data)
                {
                    foreach (var dataChannelWithUnitse in data)
                    {
                        //Apply generated delegate
                    }
                };

     Action<TestObject> overallAction = delegate(TestObject data)
                {
                    var x = getListFunc.Invoke(data);
                    listAction.Invoke(x as List<Class>);
                };

感谢任何帮助,以帮助我了解如何做到这一点 .

我目前得到这个与 variable 'Input' of type 'TestObject' referenced from scope '', but it is not defined 异常

var typeParam = Expression.Parameter(type, "Input");
    var listVariable = Expression.Variable(memberExpression.Type, "List");
    var enumerator = Expression.Variable(typeof(IEnumerator<>).MakeGenericType(dataType));


    var enumeratorType = typeof(IEnumerator<>).MakeGenericType(dataType);
    var enumerableType = typeof(IEnumerable<>).MakeGenericType(dataType);
    var enumerableParam = Expression.Parameter(enumerableType, "ExtractedCollection");

    var getEnumeratorFunc = Expression.Call(enumerableParam, enumerableType.GetMethod("GetEnumerator"));
    var getEnumeratorLambda = Expression.Lambda(getEnumeratorFunc, enumerableParam);

    var t1 = Expression.Assign(listVariable, Expression.Invoke(getListLambda, typeParam));
    var t2 = Expression.Assign(enumerator, Expression.Invoke(getEnumeratorLambda, listVariable));


    var @break = Expression.Label();

    var funcBlock = Expression.Block(
        new ParameterExpression[] { listVariable, enumerator},

   t1,
   t2,

    Expression.Loop(
        Expression.IfThenElse(

            Expression.NotEqual(Expression.Call(enumerator,typeof(IEnumerator).GetMethod("MoveNext")),Expression.Constant(false)),
                                Expression.Invoke(enumerableExpressions[0],Expression.Property(enumerator, "Current")),

                      Expression.Break(@break))
            , @break), typeParam);



    Expression<Action<TestObject>> lm = Expression.Lambda<Action<TestObject>>(funcBlock,recordExpression);
    var d = lm.Compile(); **//this is exceptioning with " variable 'Input' of type 'TestObject' referenced from scope '', but it is not defined**

2 回答

  • 16

    我在你问题的中间某处迷路了(如果我再潜入它),但我认为这就是你所追求的:

    public static Expression ForEach(Expression collection, ParameterExpression loopVar, Expression loopContent)
    {
        var elementType = loopVar.Type;
        var enumerableType = typeof(IEnumerable<>).MakeGenericType(elementType);
        var enumeratorType = typeof(IEnumerator<>).MakeGenericType(elementType);
    
        var enumeratorVar = Expression.Variable(enumeratorType, "enumerator");
        var getEnumeratorCall = Expression.Call(collection, enumerableType.GetMethod("GetEnumerator"));
        var enumeratorAssign = Expression.Assign(enumeratorVar, getEnumeratorCall);
    
        // The MoveNext method's actually on IEnumerator, not IEnumerator<T>
        var moveNextCall = Expression.Call(enumeratorVar, typeof(IEnumerator).GetMethod("MoveNext"));
    
        var breakLabel = Expression.Label("LoopBreak");
    
        var loop = Expression.Block(new[] { enumeratorVar },
            enumeratorAssign,
            Expression.Loop(
                Expression.IfThenElse(
                    Expression.Equal(moveNextCall, Expression.Constant(true)),
                    Expression.Block(new[] { loopVar },
                        Expression.Assign(loopVar, Expression.Property(enumeratorVar, "Current")),
                        loopContent
                    ),
                    Expression.Break(breakLabel)
                ),
            breakLabel)
        );
    
        return loop;
    }
    

    要使用它,您需要提供一个迭代的集合,一个替换为循环体的表达式,以及一个循环体表达式使用的ParameterExpression,它将在每次循环迭代时分配给循环变量 .

    我认为有时候例子比言辞更响亮......

    var collection = Expression.Parameter(typeof(List<string>), "collection");
    var loopVar = Expression.Parameter(typeof(string), "loopVar");
    var loopBody = Expression.Call(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), loopVar);
    var loop = ForEach(collection, loopVar, loopBody);
    var compiled = Expression.Lambda<Action<List<string>>>(loop, collection).Compile();
    compiled(new List<string>() { "a", "b", "c" });
    

    编辑:正如Jeroem Mostert在评论中正确指出的那样,这并不完全反映foreach循环的行为:这将确保它处理枚举器 . (它还会为每次迭代创建一个新的循环变量实例,但这对表达式没有意义) . 如果你有足够的动力,实现这个只需要转动手柄!


    对于在家观看的人,我有一个类似的方法来生成'for'循环:

    public static Expression For(ParameterExpression loopVar, Expression initValue, Expression condition, Expression increment, Expression loopContent)
    {
        var initAssign = Expression.Assign(loopVar, initValue);
    
        var breakLabel = Expression.Label("LoopBreak");
    
        var loop = Expression.Block(new[] { loopVar },
            initAssign,
            Expression.Loop(
                Expression.IfThenElse(
                    condition,
                    Expression.Block(
                        loopContent,
                        increment
                    ),
                    Expression.Break(breakLabel)
                ),
            breakLabel)
        );
    
        return loop;
    }
    

    这相当于以下语句,其中伪变量与上述方法中的表达式匹配:

    for (loopVar = initValue; condition; increment)
    {
        loopContent
    }
    

    同样,loopContent,condition和increment是使用loopVar的表达式,并且在每次迭代时都分配了loopVar .

  • 0

    这是canton7's excellent solution的略微扩展版本,考虑到有关处理枚举器的评论:

    public static Expression ForEach(Expression enumerable, ParameterExpression loopVar, Expression loopContent)
    {
        var elementType = loopVar.Type;
        var enumerableType = typeof(IEnumerable<>).MakeGenericType(elementType);
        var enumeratorType = typeof(IEnumerator<>).MakeGenericType(elementType);
    
        var enumeratorVar = Expression.Variable(enumeratorType, "enumerator");
        var getEnumeratorCall = Expression.Call(enumerable, enumerableType.GetMethod("GetEnumerator"));
        var enumeratorAssign = Expression.Assign(enumeratorVar, getEnumeratorCall);
        var enumeratorDispose = Expression.Call(enumeratorVar, typeof(IDisposable).GetMethod("Dispose"));
    
        // The MoveNext method's actually on IEnumerator, not IEnumerator<T>
        var moveNextCall = Expression.Call(enumeratorVar, typeof(IEnumerator).GetMethod("MoveNext"));
    
        var breakLabel = Expression.Label("LoopBreak");
    
        var trueConstant = Expression.Constant(true);
    
        var loop =
            Expression.Loop(
                Expression.IfThenElse(
                    Expression.Equal(moveNextCall, trueConstant),
                    Expression.Block(
                        new[] { loopVar },
                        Expression.Assign(loopVar, Expression.Property(enumeratorVar, "Current")),
                        loopContent),
                    Expression.Break(breakLabel)),
                breakLabel);
    
        var tryFinally =
            Expression.TryFinally(
                loop,
                enumeratorDispose);
    
        var body =
            Expression.Block(
                new[] { enumeratorVar },
                enumeratorAssign,
                tryFinally);
    
        return body;
    }
    

相关问题