2012-05-19 14 views
6

Ich bin Ausdrucksbaum erstellen und es gibt eine Situation, wo ich ein Lambda in einem anderen Lambda erstellen und innere in einer Klasse speichern und diese Klasse in Ausdrucksbaum hinzufügen müssen. Dies ist einfaches Beispiel, was ich versuche zu tun (dieser Code nicht kompiliert):Ausdruck Baum - kompilieren inneren Lambda in äußeren Lambda - Scoping Auflösung

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

Problem in GetInnerLambda().Compile() Linie in GetOuterLambda Methode. Ich bin mir einer Lösung bewusst - sie ist im kommentierten Teil des Codes. Damit funktioniert alles gut, aber ich brauche einen Wrapper als Rückgabewert, nicht Ausdruck Teilbaum (es könnte ok sein, den inneren Lambda-Teilbaum in LambdaWrapper zu speichern und später zu kompilieren, aber das gleiche Problem tritt auf).

Fehler Ich bekomme ist Unhandled Exception: System.InvalidOperationException: variable 'Param' of type 'System.Object' referenced from scope '', but it is not defined.

Wenn ich Param hinzufügen, um Variablen in innerem Lambda zu blockieren, kompiliert Code kompiliert, aber Param hat Wert in äußerem Lambda nicht zugewiesen (und das macht Sinn).

Wie kann das gelöst werden?

Antwort

0

Mit Hilfe von Balazs Tihanyi habe ich eine Lösung gefunden, die genau so funktioniert, wie ich sie brauche. Es ist ein bisschen mehr Arbeit, weil ich Binder erstellen musste, aber ich hatte mein Hauptprojekt, das ich bereits hatte, also habe ich Dummy-Binder für dieses Beispiel erstellt, um zu arbeiten.

Dies ist meine letzte Lösung:

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

Nun, da Sie nicht Param als ein konstanter Wert in Ihrem inneren Lambda-Ausdruck verwenden können, empfehle ich Ihnen, einen Lambda-Parameter auf Ihren Ausdruck hinzuzufügen:

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

dann wird der Wert des Parameters speichern in Ihrem LambdaWrapper Klasse, und verwenden sie es später als Argument in dem DynamicInvoke Aufruf:

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

das funktioniert, aber das einzige Problem ist, dass es WriteLine auf Param nennen, die eine Paramet erExpression-Objekt. Um dies zu lösen, haben Sie die Wrapper-Klasse erstellen dynamisch in Ihrem Ausdruck Baum:

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

Dann wird es den zugewiesenen Wert Param verwenden. Und da Sie Param außerhalb von GetOuterLambda nicht verwenden, können Sie es jetzt als lokale Variable verwenden.

EDIT:

Hier mein zweiter Versuch, dieses Problem zu lösen:

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

Dieser Ansatz stellt dieses innere Lambda, wenn Sie den äußeren Delegaten ausgeführt werden. Dadurch wird Param zugewiesen, bevor das innere Lambda kompiliert wird.

+0

Danke für die Antwort.Was ich an diesem Ansatz nicht mag, ist, dass diese Lambdas tatsächliche Funktionen sind, und sie können ihre Parameter haben (ich habe diesen Teil nicht in Frage gestellt, weil ich damit kein Problem habe), und Param ist nicht nur Variable muss ich zugreifen (es kann eine Menge von ihnen sein), so denke ich nicht, dass addig künstliche Parameter zur Auflösung von Scoping ist sehr elegante Lösung. –

+0

Aktualisierte Antwort könnte nur für mich arbeiten, aber ich muss überprüfen, wenn ich zurück zu meinem Arbeitscomputer komme. Danke ... –

+0

Ich habe überprüft, ob das für mich funktioniert. Fast :). Ich ging noch einen Schritt weiter und erstellte DynamicExpression zum Erstellen einer Instanz von LambdaWrapper. Ich musste Binder erstellen, also benötigt diese Soulution mehr Arbeit, aber ich hatte sie sowieso schon in meinem Hauptprojekt. Vielen Dank für Ihr Interesse an der Lösung dieses Problems :) –