2012-05-19 13 views
6

Tôi đang tạo cây biểu thức và có một tình huống mà tôi cần phải tạo một lambda trong lambda khác và lưu trữ bên trong một lớp và thêm lớp đó trong cây biểu thức. Đây là ví dụ đơn giản về những gì tôi đang cố gắng để làm (mã này không biên dịch):Biểu thức cây - biên dịch bên trong lambda - phạm vi độ phân giải

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Linq.Expressions; 

namespace SimpleTest { 
    public class LambdaWrapper { 
     private Delegate compiledLambda; 
     public LambdaWrapper(Delegate compiledLambda) { 
      this.compiledLambda = compiledLambda; 
     } 
     public dynamic Execute() { 
      return compiledLambda.DynamicInvoke(); 
     } 
    } 

    public class ForSO { 

     public ParameterExpression Param; 

     public LambdaExpression GetOuterLambda() { 
      IList<Expression> lambdaBody = new List<Expression>(); 
      Param = Expression.Parameter(typeof(object), "Param"); 
      lambdaBody.Add(Expression.Assign(
          Param, 
          Expression.Constant("Value of 'param' valiable")) 
         ); 

      lambdaBody.Add(Expression.Call(
          null, 
          typeof(ForSO).GetMethod("Write"), 
          Param) 
         ); 

      Delegate compiledInnerLambda = GetInnerLambda().Compile(); 
      LambdaWrapper wrapper = new LambdaWrapper(compiledInnerLambda); 
      lambdaBody.Add(Expression.Constant(wrapper)); 
      //lambdaBody.Add(GetInnerLambda()); 
      return Expression.Lambda(
         Expression.Block(
           new ParameterExpression[] { Param }, 
           lambdaBody)); 
     } 

     public LambdaExpression GetInnerLambda() { 
      return Expression.Lambda(
        Expression.Block(
         Expression.Call(null, 
           typeof(ForSO).GetMethod("Write"), 
           Expression.Constant("Inner lambda start")), 
         Expression.Call(null, 
           typeof(ForSO).GetMethod("Write"), 
           Param), 
         Expression.Call(null, 
           typeof(ForSO).GetMethod("Write"), 
           Expression.Constant("Inner lambda end")) 
        ) 
       ); 
     } 

     public static void Write(object toWrite) { 
      Console.WriteLine(toWrite); 
     } 

     public static void Main(string[] args) { 
      ForSO so = new ForSO(); 
      LambdaWrapper wrapper = so.GetOuterLambda().Compile() 
             .DynamicInvoke() as LambdaWrapper; 
      wrapper.Execute(); 
      //(so.GetOuterLambda().Compile().DynamicInvoke() as Delegate).DynamicInvoke(); 
     } 
    } 
} 

Vấn đề là ở GetInnerLambda().Compile() dòng trong GetOuterLambda phương pháp. Tôi biết một giải pháp - đó là phần nhận xét của mã. Với điều đó, mọi thứ hoạt động tốt, nhưng tôi cần một wrapper như giá trị trả về, không phải là subtree biểu thức (có thể là ok để lưu trữ bên trong lambda subtree trong LambdaWrapper, và biên dịch nó sau này, nhưng cùng một vấn đề xảy ra).

Lỗi tôi nhận được là Unhandled Exception: System.InvalidOperationException: variable 'Param' of type 'System.Object' referenced from scope '', but it is not defined.

Nếu tôi thêm Param để chặn biến trong lambda bên trong, biên dịch mã, nhưng Param không có giá trị được chỉ định trong lambda bên ngoài (và điều đó có ý nghĩa).

Cách này có thể được giải quyết?

Trả lời

0

Với sự trợ giúp của Balazs Tihanyi, tôi đã tìm ra giải pháp phù hợp với mình khi cần. Đó là một công việc nhiều hơn một chút bởi vì tôi đã phải tạo ra chất kết dính, nhưng tôi dự án chính của tôi, tôi đã có chúng, vì vậy tôi tạo ra các chất kết dính giả để ví dụ này hoạt động.

Đây là giải pháp cuối cùng của tôi:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Linq.Expressions; 
using System.Reflection; 
using System.Dynamic; 


namespace SimpleTest { 
    public class MyCreateBinder : CreateInstanceBinder { 
     public MyCreateBinder(CallInfo info) : base(info) { } 

     public override DynamicMetaObject FallbackCreateInstance(
             DynamicMetaObject target, 
             DynamicMetaObject[] args, 
             DynamicMetaObject errorSuggestion) { 
      var param = args[0].Value; 

      Type toCreate = target.Value as Type; 
      var ctors = toCreate.GetConstructors() 
         .Where(c => c.GetParameters().Length == args.Length) 
         .ToArray(); 

      if (ctors.Length == 0) 
       throw 
        new Exception(
         String.Format(
         "Can not find constructor for '{0}' with {1} parameters", 
         toCreate, args.Length)); 
      ConstructorInfo ctorToUse = ctors[0]; 
      return new DynamicMetaObject(
          Expression.New(
           ctorToUse, 
           args.Select(a => a.Expression).ToList()), 
         BindingRestrictions.Empty); 
     } 
    } 

    public class MySetMemberBinder : SetMemberBinder { 

     public MySetMemberBinder(string name) : base(name, false) { } 

     public override DynamicMetaObject FallbackSetMember(
           DynamicMetaObject target, 
           DynamicMetaObject value, 
           DynamicMetaObject errorSuggestion) { 

      throw new NotImplementedException(); 
     } 
    } 

    public class MyGetMemberBinder : GetMemberBinder { 
     public MyGetMemberBinder(string name) : base(name, false) { } 

     public override DynamicMetaObject FallbackGetMember(
             DynamicMetaObject target, 
             DynamicMetaObject errorSuggestion) { 
      throw new NotImplementedException(); 
     } 
    } 

    public class MyInvokeMemberBinder : InvokeMemberBinder { 
     public MyInvokeMemberBinder(string name, CallInfo callInfo) 
      : base(name, false, callInfo) { } 

     public override DynamicMetaObject FallbackInvokeMember(
            DynamicMetaObject target, 
            DynamicMetaObject[] args, 
            DynamicMetaObject errorSuggestion) { 
      var a = this; 
      throw new NotImplementedException(); 
     } 

     public override DynamicMetaObject FallbackInvoke(
            DynamicMetaObject target, 
            DynamicMetaObject[] args, 
            DynamicMetaObject errorSuggestion) { 
      throw new NotImplementedException(); 
     } 
    } 

    public class LambdaWrapper : IDynamicMetaObjectProvider { 
     private Delegate compiledLambda; 
     private LambdaExpression exp; 

     public LambdaWrapper(LambdaExpression exp) { 
      this.exp = exp; 
      this.compiledLambda = exp.Compile(); 
     } 
     public dynamic Execute(dynamic param) { 
      return compiledLambda.DynamicInvoke(param); 
     } 

     public DynamicMetaObject GetMetaObject(Expression parameter) { 
      return new MetaLambdaWrapper(parameter, this); 
     } 
    } 

    public class MetaLambdaWrapper : DynamicMetaObject { 
     public MetaLambdaWrapper(Expression parameter, object value) : 
      base(parameter, BindingRestrictions.Empty, value) { } 

     public override DynamicMetaObject BindInvokeMember(
            InvokeMemberBinder binder, 
            DynamicMetaObject[] args) { 
      MethodInfo method = this.Value.GetType().GetMethod(binder.Name); 
      return new DynamicMetaObject(
         Expression.Call(
          Expression.Constant(this.Value), 
           method, 
            args.Select(a => a.Expression)), 
         BindingRestrictions.GetTypeRestriction(
          this.Expression, 
          typeof(LambdaWrapper))); 
     } 
    } 


    public class ForSO { 
     public ParameterExpression Param; 
     public LambdaExpression GetOuterLambda() { 
      Expression wrapper; 
      IList<Expression> lambdaBody = new List<Expression>(); 
      Param = Expression.Parameter(typeof(object), "Param"); 
      lambdaBody.Add(Expression.Assign(
          Param, 
          Expression.Constant("Value of 'param' variable")) 
         ); 
      lambdaBody.Add(Expression.Call(
          null, 
          typeof(ForSO).GetMethod("Write"), 
          Param) 
         ); 

      wrapper = Expression.Dynamic(
           new MyCreateBinder(new CallInfo(1)), 
           typeof(object), 
           Expression.Constant(typeof(LambdaWrapper)), 
           Expression.Quote(GetInnerLambda())); 


      lambdaBody.Add(
       Expression.Dynamic(
        new MyInvokeMemberBinder("Execute", new CallInfo(1)), 
        typeof(object), 
        wrapper, 
       Expression.Constant("calling inner lambda from outer"))); 

      lambdaBody.Add(wrapper); 

      return Expression.Lambda(
         Expression.Block(
           new ParameterExpression[] { Param }, 
           lambdaBody)); 
     } 

     public LambdaExpression GetInnerLambda() { 
      ParameterExpression innerParam = Expression.Parameter(
               typeof(object), 
               "innerParam"); 
      return Expression.Lambda(
        Expression.Block(
         Expression.Call(null, 
           typeof(ForSO).GetMethod("Write"), 
           Expression.Constant("Inner lambda start")), 
         Expression.Call(null, 
           typeof(ForSO).GetMethod("Write"), 
           innerParam), 
         Expression.Call(null, 
           typeof(ForSO).GetMethod("Write"), 
           Param), 
         Expression.Call(null, 
           typeof(ForSO).GetMethod("Write"), 
           Expression.Constant("Inner lambda end")) 
        ), 
        innerParam 
       ); 
     } 

     public static void Write(object toWrite) { 
      Console.WriteLine(toWrite); 
     } 

     public static void Main(string[] args) { 
      Console.WriteLine("-----------------------------------"); 
      ForSO so = new ForSO(); 

      LambdaWrapper wrapper = (LambdaWrapper) so.GetOuterLambda() 
                .Compile() 
                .DynamicInvoke(); 
      Console.WriteLine("-----------------------------------"); 
      wrapper.Execute("Calling from main"); 
     } 
    } 

} 
1

Vâng, kể từ khi bạn không thể sử dụng Param như một giá trị không đổi trong biểu thức lambda bên trong của bạn, tôi khuyên bạn nên để thêm một tham số lambda để biểu của bạn:

public LambdaExpression GetInnerLambda() 
{ 
    var param = Expression.Parameter(typeof(object)); 
    return Expression.Lambda(
     Expression.Block(
      Expression.Call(null, 
       typeof(ForSO).GetMethod("Write"), 
       Expression.Constant("Inner lambda start")), 
      Expression.Call(null, 
       typeof(ForSO).GetMethod("Write"), 
       param), 
      Expression.Call(null, 
       typeof(ForSO).GetMethod("Write"), 
       Expression.Constant("Inner lambda end")) 
     ), 
     param 
    ); 
} 

Sau đó lưu trữ giá trị của tham số trong LambdaWrapper của bạn lớp, và sử dụng nó sau này như là một cuộc tranh cãi trong DynamicInvoke gọi:

public class LambdaWrapper 
{ 
    private object param; 
    private Delegate compiledLambda; 

    public LambdaWrapper(Delegate compiledLambda, object param) 
    { 
     this.compiledLambda = compiledLambda; 
     this.param = param; 
    } 

    public dynamic Execute() 
    { 
     return compiledLambda.DynamicInvoke(param); 
    } 
} 

đó làm việc, nhưng vấn đề duy nhất là nó sẽ gọi WriteLine trên Param, mà là một Paramet đối tượng erExpression. Để giải quyết điều này, bạn phải tạo lớp trình bao bọc động trong cây biểu thức của mình:

//lambdaBody.Add(Expression.Constant(wrapper)); 
lambdaBody.Add(Expression.New(
    typeof(LambdaWrapper).GetConstructor(new[] { typeof(Delegate), typeof(object) }), 
    Expression.Constant(compiledInnerLambda), 
    Param) 
); 

Sau đó, nó sẽ sử dụng giá trị được gán là Param. Và vì bạn không sử dụng Param bên ngoài GetOuterLambda, giờ đây bạn có thể sử dụng nó làm biến cục bộ.

EDIT:

Đây là nỗ lực thứ hai của tôi để giải quyết vấn đề này:

public LambdaExpression GetOuterLambda() 
{ 
    ... 
    //Delegate compiledInnerLambda = GetInnerLambda().Compile(); 
    //LambdaWrapper wrapper = new LambdaWrapper(compiledInnerLambda); 

    lambdaBody.Add(Expression.New(
     typeof(LambdaWrapper).GetConstructor(new[] { typeof(Delegate) }), 
     Expression.Call(
      Expression.Call(
       typeof(ForSO).GetMethod("GetInnerLambda", BindingFlags.Public | BindingFlags.Static), 
       Param 
      ), 
      typeof(LambdaExpression).GetMethod("Compile", Type.EmptyTypes) 
     ) 
    )); 
    ... 
} 

public static LambdaExpression GetInnerLambda(object param) 
{ 
    return Expression.Lambda(
     Expression.Block(
      Expression.Call(null, 
       typeof(ForSO).GetMethod("Write"), 
       Expression.Constant("Inner lambda start")), 
      Expression.Call(null, 
       typeof(ForSO).GetMethod("Write"), 
       Expression.Constant(param)), 
      Expression.Call(null, 
       typeof(ForSO).GetMethod("Write"), 
       Expression.Constant("Inner lambda end")) 
     ) 
    ); 
} 

Cách tiếp cận này biên dịch lambda bên này khi bạn chạy các đại biểu bên ngoài. Bằng cách này, Param sẽ được gán trước khi lambda bên trong được biên dịch.

+0

Cảm ơn cho câu trả lời.Những gì tôi không thích về cách tiếp cận này là những lambdas là các chức năng thực tế, và chúng có thể có các tham số của chúng (tôi không bao gồm phần đó trong câu hỏi vì tôi không có vấn đề gì), và Param không chỉ biến tôi cần phải truy cập (có thể có rất nhiều người trong số họ), vì vậy tôi không nghĩ rằng các thông số nhân tạo addig để giải quyết phạm vi là giải pháp rất thanh lịch. –

+0

Câu trả lời được cập nhật có thể chỉ hoạt động với tôi, nhưng tôi sẽ phải kiểm tra khi nào tôi quay lại máy tính đang hoạt động. Cảm ơn ... –

+0

Tôi đã kiểm tra xem điều này có phù hợp với tôi hay không. Hầu hết :). Tôi đã lấy nó một bước xa hơn và tạo ra DynamicExpression để tạo ra thể hiện của LambdaWrapper. Tôi đã phải tạo ra các chất kết dính, vì vậy, linh hồn này đòi hỏi nhiều công việc hơn, nhưng tôi đã có chúng trong dự án chính của tôi. Cảm ơn bạn đã quan tâm đến việc giải thích vấn đề này :) –