2012-05-19 9 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() कतार में है। मुझे एक समाधान के बारे में पता है - यह कोड के हिस्से में टिप्पणी की गई है। इसके साथ, सबकुछ ठीक काम करता है, लेकिन मुझे रिटर्न वैल्यू के रूप में एक रैपर की आवश्यकता होती है, अभिव्यक्ति subtree नहीं (यह 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 
    ); 
} 

तब पैरामीटर का मान संग्रहीत:

उत्तर

0

बालाज़ तिहानी से सहायता के साथ मुझे समाधान मिला जो वास्तव में मेरे लिए काम करता है। यह थोड़ा और काम है क्योंकि मुझे बाइंडर्स बनाना था, लेकिन मैं अपनी मुख्य परियोजना में पहले से ही था, इसलिए मैंने काम करने के लिए इस उदाहरण के लिए डमी बाइंडर्स बनाए।

यह मेरा अंतिम समाधान है:

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 उपयोग नहीं कर सकते, मैं तुम्हें अपने अभिव्यक्ति के लिए एक लैम्ब्डा पैरामीटर जोड़ने का सुझाव देते हैं वर्ग, और बाद में उसका उपयोग DynamicInvoke कॉल में एक तर्क के रूप:

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

काम करता है यही कारण है कि है, लेकिन केवल मुद्दा यह है कि यह Param पर WriteLine फोन करेगा, जो एक paramet है erExpression वस्तु। इस समस्या के समाधान के लिए आपको अपनी अभिव्यक्ति पेड़ में गतिशील रूप से आवरण वर्ग बनाने के लिए:

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

तो यह Param की निर्दिष्ट मान का उपयोग होगा। और चूंकि आप के बाहर Param का उपयोग नहीं करते हैं, तो अब आप इसे स्थानीय चर के रूप में उपयोग कर सकते हैं।

संपादित करें:

यहाँ मेरी दूसरी इस समस्या को हल करने के लिए प्रयास है:

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

जवाब के लिए धन्यवाद।मुझे इस दृष्टिकोण के बारे में क्या पसंद नहीं है यह है कि वे लैम्बडा वास्तविक कार्य हैं, और उनके पास पैरामीटर हो सकते हैं (मैंने उस भाग को प्रश्न में शामिल नहीं किया क्योंकि मुझे इसमें कोई समस्या नहीं है), और परम न केवल परिवर्तनीय मुझे एक्सेस करने की आवश्यकता है (उनमें से बहुत कुछ हो सकता है), इसलिए मुझे नहीं लगता कि स्कोपिंग को हल करने के लिए एडिग कृत्रिम पैरामीटर बहुत ही सुरुचिपूर्ण समाधान है। –

+0

अपडेट किया गया उत्तर सिर्फ मेरे लिए काम कर सकता है, लेकिन मुझे यह जांचना होगा कि मैं अपने कामकाजी कंप्यूटर पर वापस कब आऊंगा। धन्यवाद ... –

+0

मैंने जांच की कि यह मेरे लिए काम करता है या नहीं। लगभग :)। मैंने इसे एक कदम आगे ले लिया और LambdaWrapper के उदाहरण बनाने के लिए डायनामिक एक्स्प्रेशन बनाया। मुझे बाइंडर्स बनाना पड़ा, इसलिए इस आत्मा को अधिक काम की आवश्यकता है, लेकिन मेरे पास पहले से ही मेरे मुख्य प्रोजेक्ट में था। इस मुद्दे को हल करने में आपके द्वारा दिखाए गए ब्याज के लिए धन्यवाद :) –

संबंधित मुद्दे