【问题标题】:Foreach loop using Expression trees使用表达式树的 Foreach 循环
【发布时间】:2015-01-26 08:09:14
【问题描述】:

我已经看到了 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; }
    }

在运行时,我遍历每个属性并生成一个委托,该委托将转换为该属性的字符串。我已经完成了所有工作。我现在必须处理的问题是,对于 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>);
                };

感谢任何帮助以帮助我了解如何执行此操作。

我目前有这个例外,从范围''引用的'TestObject'类型的变量'Input',但它没有定义

    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**

【问题讨论】:

  • List&lt;T&gt; 实际上有一个您可以调用的 .ForEach() 方法。这并不适用于任何IEnumerable,但在这种特殊情况下,它可以帮助您简化代码。

标签: c# .net lambda expression-trees


【解决方案1】:

我在你的问题中间迷路了(如果我的解释有误,请告诉我,我会再深入研究它),但我认为这就是你追求的是:

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 在 cmets 中正确指出的那样,这并没有相当反映 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。

【讨论】:

  • 这与真正的foreach 几乎但不完全一样:IEnumerator&lt;T&gt; 是一次性的,而实际的foreach 会处理它。当然,绝大多数枚举器都有什么都不做的实现(包括所有标准集合类的实现),这通常可以正常工作。 foreach 的完整语义实际上非常复杂,因为根本不需要接口(@98​​7654321@ 已过时,但语言规范给出了细节);我不一定建议实施,除非是一个挑战。 :-)
  • 这是一个很好的观点,如果我没记错的话,还有一个 try/finally 围绕着 Dispose 操作!我会在我的回答中为此效果添加注释
  • 非常感谢大家,被转移到别的东西上,刚刚回到它,它完美地工作! @canton7
【解决方案2】:

relatively_random's solution 很棒,但foreach 可以处理其他几种情况。检查这些指向 SharpLab 的链接以验证每个链接中生成的内容:

GetEnumerator() 返回的类型的使用非常重要,因此值类型枚举器不会被装箱。 System.Collections.Generic 中的所有集合都有值类型枚举器,因为对其方法的调用不是虚拟的,因此性能要好得多。

将所有内容放在一起会产生以下代码:

static partial class ExpressionEx
{
    public static Expression ForEach<TSource>(Expression enumerable, Expression loopContent)
    {
        var enumerableType = enumerable.Type;
        var getEnumerator = enumerableType.GetMethod("GetEnumerator");
        if (getEnumerator is null)
            getEnumerator = typeof(IEnumerable<>).MakeGenericType(typeof(TSource)).GetMethod("GetEnumerator");
        var enumeratorType = getEnumerator.ReturnType;
        var enumerator = Expression.Variable(enumeratorType, "enumerator");

        return Expression.Block(new[] { enumerator },
            Expression.Assign(enumerator, Expression.Call(enumerable, getEnumerator)),
            EnumerationLoop(enumerator, loopContent));
    }

    public static Expression ForEach<TSource>(Expression enumerable, ParameterExpression loopVar, Expression loopContent)
    {
        var enumerableType = enumerable.Type;
        var getEnumerator = enumerableType.GetMethod("GetEnumerator");
        if (getEnumerator is null)
            getEnumerator = typeof(IEnumerable<>).MakeGenericType(typeof(TSource)).GetMethod("GetEnumerator");
        var enumeratorType = getEnumerator.ReturnType;
        var enumerator = Expression.Variable(enumeratorType, "enumerator");

        return Expression.Block(new[] { enumerator },
            Expression.Assign(enumerator, Expression.Call(enumerable, getEnumerator)),
            EnumerationLoop(enumerator,
                Expression.Block(new[] { loopVar },
                    Expression.Assign(loopVar, Expression.Property(enumerator, "Current")),
                    loopContent)));
    }

    static Expression EnumerationLoop(ParameterExpression enumerator, Expression loopContent)
    {
        var loop = While(
            Expression.Call(enumerator, typeof(IEnumerator).GetMethod("MoveNext")),
            loopContent);

        var enumeratorType = enumerator.Type;
        if (typeof(IDisposable).IsAssignableFrom(enumeratorType))
            return Using(enumerator, loop);

        if (!enumeratorType.IsValueType)
        {
            var disposable = Expression.Variable(typeof(IDisposable), "disposable");
            return Expression.TryFinally(
                loop,
                Expression.Block(new[] { disposable },
                    Expression.Assign(disposable, Expression.TypeAs(enumerator, typeof(IDisposable))),
                    Expression.IfThen(
                        Expression.NotEqual(disposable, Expression.Constant(null)),
                        Expression.Call(disposable, typeof(IDisposable).GetMethod("Dispose")))));
        }

        return loop;
    }

    public static Expression Using(ParameterExpression variable, Expression content)
    {
        var variableType = variable.Type;

        if (!typeof(IDisposable).IsAssignableFrom(variableType))
            throw new Exception($"'{variableType.FullName}': type used in a using statement must be implicitly convertible to 'System.IDisposable'");

        var getMethod = typeof(IDisposable).GetMethod("Dispose");

        if (variableType.IsValueType)
        {
            return Expression.TryFinally(
                content,
                Expression.Call(Expression.Convert(variable, typeof(IDisposable)), getMethod));
        }

        if (variableType.IsInterface)
        {
            return Expression.TryFinally(
                content,
                Expression.IfThen(
                    Expression.NotEqual(variable, Expression.Constant(null)),
                    Expression.Call(variable, getMethod)));
        }

        return Expression.TryFinally(
            content,
            Expression.IfThen(
                Expression.NotEqual(variable, Expression.Constant(null)),
                Expression.Call(Expression.Convert(variable, typeof(IDisposable)), getMethod)));
    }

    public static Expression While(Expression loopCondition, Expression loopContent)
    {
        var breakLabel = Expression.Label();
        return Expression.Loop(
            Expression.IfThenElse(
                loopCondition,
                loopContent,
                Expression.Break(breakLabel)),
            breakLabel);
    }
}

没有loopVarForEach 对于枚举而不获取项目很有用。 Count() 实现就是这种情况。

编辑:NetFabric.Reflection NuGet 包中提供了更新和测试的版本。查看其repository 获取源代码。

【讨论】:

    【解决方案3】:

    这里是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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-08-07
      • 2021-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多