2009-12-20 52 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())都依賴於它。