2009-12-20 12 views
12

Cập nhật Câu hỏi tiếp theo XuốngBiểu/Tuyên Bố

Tôi đã thử nghiệm với cây biểu hiện trong .NET 4 để tạo ra mã trong thời gian chạy và tôi đã cố gắng để thực hiện báo cáo kết quả foreach bằng cách xây dựng một cây biểu .

Cuối cùng, khái niệm sẽ có thể tạo ra một đại biểu mà thực hiện điều này:

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

tôi đã đi lên với phương pháp helper sau đó tạo ra một BlockExpression từ một IEnumerable:

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; 

} 

Các mã sau đây:

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")); 

Tạo cây biểu thức này:

.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: 
    } 
} 

này có vẻ là OK, nhưng gọi Compile trên mà kết quả biểu hiện trong một ngoại lệ:

"variable 'enumerator' of type 'System.Collections.Generic.IEnumerator`1[System.Int32]' referenced from scope '', but it is not defined" 

Không phải tôi định nghĩa nó ở đây:

var enumerator = Expression.Variable(typeof(IEnumerator<T>), "enumerator"); 

?

Tất nhiên, ví dụ ở đây được giả tạo và chưa sử dụng thực tế, nhưng tôi đang cố gắng thu thập các cây biểu thức có thân, để kết hợp động chúng vào thời gian chạy trong tương lai.


EDIT: vấn đề ban đầu của tôi đã được giải quyết bởi Alexandra, cảm ơn! Tất nhiên, tôi đã gặp phải vấn đề tiếp theo ngay bây giờ. Tôi đã khai báo một số BlockExpression có một biến trong đó. Bên trong biểu thức đó, tôi muốn một biểu thức khác tham chiếu biến đó. Nhưng tôi không có một tham chiếu thực sự cho biến đó, chỉ là tên của nó, bởi vì biểu thức được cung cấp bên ngoài.

var param = Expression.Variable(typeof(IEnumerable<T>), "something"); 

var block = Expression.Block(
       new [] { param }, 
       body 
      ); 

Biến body được thông qua tại bên ngoài và không có tài liệu tham khảo trực tiếp đến param, nhưng không biết tên của các biến trong biểu thức ("something"). Nó trông giống như thế này:

var body = Expression.Call(typeof(Console).GetMethod("WriteLine",new[] { typeof(bool) }), 
       Expression.Equal(Expression.Parameter(typeof(IEnumerable<int>), "something"), Expression.Constant(null))); 

Đây là "mã" rằng đây tạo:

.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) 
    } 
} 

Tuy nhiên, nó không biên dịch. Với cùng một lỗi như trước.

TLDR: Làm cách nào để tham chiếu biến bằng số nhận dạng trong cây biểu thức?

+0

Tôi nghĩ bạn không thể làm điều này. Tên mà bạn cung cấp cho các tham số bên trong cây biểu thức giống như tên thân thiện hơn. Bạn thực sự không cần chúng, bạn có thể tạo một tham số không có tên và hệ thống sẽ tạo ra một cái gì đó cho bạn. Nhưng điều này là nhiều hơn cho mục đích gỡ lỗi hơn cho bất cứ điều gì khác. Vì vậy, bạn chỉ cần tạo hai tham số có cùng tên chứ không phải tham số và tham chiếu đến nó. Tôi sẽ đưa ví dụ của bạn đến nhóm DLR và hỏi xem đó có phải là lỗi hay không mà bạn có thể tạo hai thông số có cùng tên như thế này. Nhưng tôi có thể nhận được câu trả lời chỉ sau những ngày nghỉ. –

+0

Hm, do đó, không thể tự động soạn một đại biểu bằng cách thêm các bit và phần riêng biệt lại vào một cây biểu thức? Mục tiêu cuối cùng của tôi là tạo mã bằng cách sử dụng một thuật toán tiến hóa và cho điều đó, tôi thực sự cần phải có khả năng tham chiếu các biến được tạo ra trong phạm vi bên ngoài. Cảm ơn bạn đã giúp đỡ :) – JulianR

+0

Tôi không nói rằng :-) Tất nhiên bạn có thể tạo một đại biểu hoặc một phương pháp tĩnh với cây biểu thức (tôi thậm chí có một bài đăng blog về điều đó: http://blogs.msdn.com /csharpfaq/archive/2009/09/14/generating-dynamic-methods-with-expression-trees-in-visual-studio-2010.aspx) Nhưng bạn có thể cần phải cấu trúc lại đoạn mã chính xác này, để "cơ thể" "nên có tham chiếu thực sự tới" param "không chỉ là tên chuỗi. –

Trả lời

12

Bạn có vấn đề là bạn không chuyển các tham số và biến vào biểu thức khối của mình. Bạn sử dụng chúng trong các biểu thức "bên trong", nhưng biểu thức khối không biết gì về chúng. Về cơ bản, tất cả những gì bạn cần làm là chuyển tất cả các tham số và biến của bạn thành một biểu thức khối.

 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

Điều này đã ném tôi cho một vòng lặp trong một thời gian ngắn. Tôi nghĩ tham số đầu tiên trong Block được gọi là "Parameters" nhưng nó thực sự được gọi là "VariableDeclarations". Đây là chìa khóa để giải quyết nó mặc dù cảm ơn! –

2

Xin lỗi nếu điều này là thread gọi hồn, nhưng trong trường hợp người khác đang chạy vào cùng hoặc tương tự vấn đề:

Bạn có thể thử viết một ExpressionVisitor thay thế một tham số có cùng tên và gõ vào biểu thức cơ thể bên ngoài với thông số biến mà bạn đã khai báo khi tạo biểu thức khối. Bằng cách đó, tham số trong cơ thể sẽ là đối tượng giống như tham số trong khai báo khối và do đó LambdaExpression sẽ biên dịch.

+0

Đó có thể là một quan sát hữu ích, nhưng nó sẽ không thực sự giúp đỡ trong trường hợp này (thiếu khai báo các biến cục bộ trong 'Expression.Block()'). – svick

5

Đừng quên vứt bỏ IEnumerator trong lần thử/cuối cùng - rất nhiều mã (chẳng hạn như File.ReadLines()) tùy thuộc vào điều đó.