2009-12-20 68 views
12

更新问题进一步下跌表达/声明树木

我一直在表达式树实验.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

我认为你不能这样做。您在表达式树中给参数指定的名称更像是友好名称。您实际上根本不需要它们,您可以创建一个没有名称的参数,系统会为您生成一些内容。但是这对于调试目的来说比其他任何东西都要多。 因此,您只需创建两个具有相同名称的参数,而不是参数和参考。 我会把你的例子带到DLR团队,并询问是否是一个错误,你可以用同样的名字创建两个参数。但我只能在假期后才能得到答案。 – 2009-12-22 23:05:59

+0

嗯,所以无法通过在表达式树中添加单独的位和片段来动态组合委托?我的最终目标是使用演化算法生成代码,为此,我真的需要能够引用在外部作用域中创建的变量。感谢您的帮助:) – JulianR 2009-12-22 23:28:19

+0

我不说:-)当然,你可以创建一个委托或静态方法与表达式树(我甚至有一个关于该博客文章:http://blogs.msdn.com /csharpfaq/archive/2009/09/14/generating-dynamic-methods-with-expression-trees-in-visual-studio-2010.aspx) 但您可能需要重构这段精确的代码,以便“body “应该真正引用”param“而不只是一个字符串名称。 – 2009-12-23 00:10:53

回答

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

这让我有一段时间的循环。我认为块中的第一个参数被称为“参数”,但它应该被称为“VariableDeclarations”。这是解决它的关键,但谢谢! – 2010-07-29 15:27:24

2

很抱歉,如果这是线程巫术,但如果其他人正在运行到相同或相似的问题:

你可以尝试写替换参数具有相同名称的ExpressionVisitor和类型使用您在创建块表达式时声明的变量参数创建外部表达式。这样,主体中的参数将与block声明中的参数是同一个对象,因此LambdaExpression应该被编译。

+0

这可能是一个有用的观察,但在这种情况下(在'Expression.Block()'中缺少局部变量的声明)实际上不会有帮助。 – svick 2014-11-29 18:04:16

5

不要忘了在try/finally中配置IEnumerator - 很多代码(比如File.ReadLines())都依赖于它。