进一步更新问题
我一直在.NET 4中尝试使用表达式树来在运行时生成代码,我一直在尝试通过构建表达式树来实现foreach
语句。
最后,表达式应该能够生成执行此操作的委托:
Action<IEnumerable<int>> action = source =>
{
var enumerator = source.GetEnumerator();
while(enumerator.MoveNext())
{
var i = enumerator.Current;
// the body of the foreach that I don't currently have yet
}
}
我想出了以下帮助器方法,它从IEnumerable生成一个BlockExpression:
public static BlockExpression ForEachExpr<T>(this IEnumerable<T> source, string collectionName, string itemName)
{
var item = Expression.Variable(typeof(T), itemName);
var enumerator = Expression.Variable(typeof(IEnumerator<T>), "enumerator");
var param = Expression.Parameter(typeof(IEnumerable<T>), collectionName);
var doMoveNext = Expression.Call(enumerator, typeof(IEnumerator).GetMethod("MoveNext"));
var assignToEnum = Expression.Assign(enumerator, Expression.Call(param, typeof(IEnumerable<T>).GetMethod("GetEnumerator")));
var assignCurrent = Expression.Assign(item, Expression.Property(enumerator, "Current"));
var @break = Expression.Label();
var @foreach = Expression.Block(
assignToEnum,
Expression.Loop(
Expression.IfThenElse(
Expression.NotEqual(doMoveNext, Expression.Constant(false)),
assignCurrent
, Expression.Break(@break))
,@break)
);
return @foreach;
}
以下代码:
var ints = new List<int> { 1, 2, 3, 4 };
var expr = ints.ForEachExpr("ints", "i");
var lambda = Expression.Lambda<Action<IEnumerable<int>>>(expr, Expression.Parameter(typeof(IEnumerable<int>), "ints"));
生成此表达式树:
.Lambda #Lambda1<System.Action`1[System.Collections.Generic.IEnumerable`1[System.Int32]]>(System.Collections.Generic.IEnumerable`1[System.Int32] $ints)
{
.Block() {
$enumerator = .Call $ints.GetEnumerator();
.Loop {
.If (.Call $enumerator.MoveNext() != False) {
$i = $enumerator.Current
} .Else {
.Break #Label1 { }
}
}
.LabelTarget #Label1:
}
}
这似乎没问题,但在该表达式上调用Compile
会导致异常:
"variable 'enumerator' of type 'System.Collections.Generic.IEnumerator`1[System.Int32]' referenced from scope '', but it is not defined"
我没有在这里定义它:
var enumerator = Expression.Variable(typeof(IEnumerator<T>), "enumerator");
当然,这里的例子是设计的,并没有实际用途,但我试图获得具有主体的表达式树的悬念,以便将来在运行时动态组合它们。
编辑:亚历山德拉解决了我最初的问题,谢谢!当然,我现在遇到了下一个问题。我已经宣布了一个BlockExpression
,其中包含一个变量。在该表达式中,我想要另一个引用该变量的表达式。但是我没有对该变量的实际引用,只是它的名称,因为表达式是从外部提供的。
var param = Expression.Variable(typeof(IEnumerable<T>), "something");
var block = Expression.Block(
new [] { param },
body
);
body
变量在外部传递,没有直接引用param
,但确实知道表达式("something"
)中变量的名称。它看起来像这样:
var body = Expression.Call(typeof(Console).GetMethod("WriteLine",new[] { typeof(bool) }),
Expression.Equal(Expression.Parameter(typeof(IEnumerable<int>), "something"), Expression.Constant(null)));
这是生成的“代码”:
.Lambda #Lambda1<System.Action`1[System.Collections.Generic.IEnumerable`1[System.Int32]]>(System.Collections.Generic.IEnumerable`1[System.Int32] $something)
{
.Block(System.Collections.Generic.IEnumerable`1[System.Int32] $something) {
.Call System.Console.WriteLine($something== null)
}
}
然而,它没有编译。与之前的错误相同。
TLDR:如何在表达式树中按标识符引用变量?
答案 0 :(得分:12)
您的问题是您没有将参数和变量传递给块表达式。您可以在“内部”表达式中使用它们,但块表达式对它们一无所知。基本上,您需要做的就是将所有参数和变量传递给块表达式。
var @foreach = Expression.Block(
new ParameterExpression[] { item, enumerator, param },
assignToEnum,
Expression.Loop(
Expression.IfThenElse(
Expression.NotEqual(doMoveNext, Expression.Constant(false)),
assignCurrent,
Expression.Break(@break))
, @break)
);
答案 1 :(得分:5)
不要忘记在try / finally中处理IEnumerator - 许多代码(例如File.ReadLines())依赖于此。
答案 2 :(得分:2)
很抱歉,如果这是线程死灵,但如果其他人遇到相同或类似的问题:
您可以尝试编写一个ExpressionVisitor,用您在创建块表达式时声明的变量参数替换外部正文表达式中具有相同名称和类型的参数。这样,正文中的参数将与块声明中的参数是同一个对象,因此LambdaExpression应该编译。