2012-05-19 10 views
6

표현 트리를 만들 때 다른 람다에서 하나의 람다를 만들고 클래스에서 내부 하나를 저장하고 해당 클래스를 표현 트리에 추가해야하는 상황이 있습니다. 이것은 내가 뭘하려고 오전의 간단한 예입니다 (이 코드는 컴파일되지 않습니다) :표현 트리 - 바깥 쪽 람다 범위 지정의 내부 람다 컴파일

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() 라인입니다. 코드의 주석 부분에 하나의 해결책이 있음을 알고 있습니다. 그 모든 일을 잘 작동하지만 반환 값으로 래퍼가 필요합니다. 식 서브 트리가 아닙니다. LambdaWrapper에 내부 람다 하위 트리를 저장하고 나중에 컴파일하면 문제가 발생하지만 같은 문제가 발생합니다.

오류가 발생했습니다. Unhandled Exception: System.InvalidOperationException: variable 'Param' of type 'System.Object' referenced from scope '', but it is not defined입니다.

내부 람다 변수를 블록에 추가하면 코드가 컴파일되지만 Param은 외부 람다에 값이 할당되지 않습니다.

어떻게 해결할 수 있습니까? 당신의 LambdaWrapper

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

그런 다음 매개 변수의 값을 저장 : 당신은 당신의 내면의 람다 식의 상수 값으로 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

글쎄, 난 당신이 표현 람다 매개 변수를 추가하는 것이 좋습니다 작동하지만, 유일한 문제는 그것이 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의 할당 된 값을 사용합니다. 그리고 ParamGetOuterLambda 외부로 사용하지 않으므로 이제이를 로컬 변수로 사용할 수 있습니다.

편집 :

여기이 문제를 해결하기 위해 내 두 번째 시도입니다 : 당신이 외부 대리자를 실행할 때

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은 변수 (많은 것들이있을 수 있습니다)에 액세스해야하므로 범위 지정을 해결하기 위해 인공 파라미터를 추가하는 것은 매우 세련된 솔루션이라고 생각하지 않습니다. –

+0

업데이트 된 답변은 저에게 효과적 일지 모르지만 근무중인 컴퓨터로 돌아올 때를 확인해야합니다. 감사합니다 ... –

+0

이것이 저에게 효과가 있는지 확인했습니다. 거의 :). 나는 한 단계 더 나아가 LambdaWrapper 인스턴스를 생성하기 위해 DynamicExpression을 만들었습니다. 나는 바인더를 만들어야했기 때문에이 영혼은 더 많은 일을 필요로하지만, 어쨌든 나는 주 프로젝트에서 그것들을 가지고 있었다. 이 문제를 해결해 주신 것에 감사드립니다. :) –