更新問題進一步下跌表達/聲明樹木
我一直在表達式樹實驗.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:如何通過表達式樹中的標識符引用變量?
我認爲你不能這樣做。您在表達式樹中給參數指定的名稱更像是友好名稱。您實際上根本不需要它們,您可以創建一個沒有名稱的參數,系統會爲您生成一些內容。但是這對於調試目的來說比其他任何東西都要多。 因此,您只需創建兩個具有相同名稱的參數,而不是參數和參考。 我會把你的例子帶到DLR團隊,並詢問是否是一個錯誤,你可以用同樣的名字創建兩個參數。但我只能在假期後才能得到答案。 – 2009-12-22 23:05:59
嗯,所以無法通過在表達式樹中添加單獨的位和片段來動態組合委託?我的最終目標是使用演化算法生成代碼,爲此,我真的需要能夠引用在外部作用域中創建的變量。感謝您的幫助:) – JulianR 2009-12-22 23:28:19
我不說:-)當然,你可以創建一個委託或靜態方法與表達式樹(我甚至有一個關於該博客文章: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