2012-05-19 5 views
6

私は式ツリーを作成しています。別のラムダで1つのラムダを作成し、クラスの内部のものを格納し、そのクラスを式ツリーに追加する必要がある状況があります。 これは私がやろうとしています何の簡単な例である(このコードはコンパイルされません):式ツリー - 外側のラムダスコープの解像度で内側のラムダをコンパイル

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(); 
     } 
    } 
} 

問題がGetOuterLambda方法でGetInnerLambda().Compile()ラインです。 私は1つの解決策を認識しています - コードのコメント部分にあります。これで、すべて正常に動作しますが、式のサブツリーではなく、戻り値としてラッパーが必要です(LambdaWrapperに内部ラムダサブツリーを格納し、後でコンパイルするのは問題ありませんが同じ問題が発生します)。

エラーが発生しましたUnhandled Exception: System.InvalidOperationException: variable 'Param' of type 'System.Object' referenced from scope '', but it is not definedです。

内側のラムダのブロック変数にParamを追加すると、コードはコンパイルされますが、Paramは外側のラムダに値が割り当てられていません(意味があります)。

どうすれば解決できますか?まあ

答えて

0

Balazs Tihanyiの助けを借りて、私が必要としているのとまったく同じ解決策を見つけました。私はバインダーを作成しなければならなかったのでもう少し作業はしましたが、私の主なプロジェクトには既にありましたので、この例のダミーバインダーを作成しました。答えを

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

、あなたはあなたの内側のラムダ式で定数値としてParamを使用することができないので、私はあなたの式にラムダパラメータを追加することをお勧め:

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

次に、あなたのLambdaWrapperにパラメータの値を格納動作しますが、唯一の問題は、それがPARAMETある、ParamWriteLineを呼び出すことです

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

:クラスでは、とDynamicInvoke呼び出しの引数として後でそれを使用しますerExpressionオブジェクト。これを解決するために、あなたはあなたの式ツリー内で動的にラッパークラスを作成する必要があります。

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

そして、それはParamの割り当てられた値を使用します。 GetOuterLambdaの外にParamを使用しないので、これをローカル変数として使用できるようになりました。

EDIT:

ここでは、この問題を解決するために、私の第二の試みである:

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

あなたが外デリゲートを実行したときに、このアプローチは、この内側のラムダをコンパイルします。これにより、内側のラムダがコンパイルされる前にParamが割り当てられます。

+0

ありがとう:

これは私の最終的な解決策です。私がこのアプローチについて気に入らないのは、それらのラムダが実際の関数であり、パラメータを持つことができるということです(私はそれに問題がないので、問題の部分は含めませんでした).Paramは、私はアクセスする必要があります(それらの多くがある可能性があります)ので、私はスコープを解決するために人工的なパラメータをaddigは非常にエレガントなソリューションだとは思わない。 –

+0

更新された回答は私にとってはうまくいくかもしれませんが、私はいつ仕事用のコンピュータに戻ってくるかを確認する必要があります。ありがとう... –

+0

これは私のために働くかどうかを確認しました。ほとんど:)私はそれをさらに一歩進め、LambdaWrapperのインスタンスを作成するためのDynamicExpressionを作成しました。私はバインダーを作る必要があったので、この作品はもっと多くの作業が必要ですが、私の主なプロジェクトではすでに持っていました。この問題を解決してくれたことに感謝します:) –

関連する問題