Expression Tree - Compile Inner Lambda in Outer Lambda Resolution

I create an expression tree, and a situation arises when I need to create one lambda in another lambda and save the inner one in the class and add this class to the expression tree. This is a simple example of what I'm trying to do (this code does not compile):

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

The problem is in the GetInnerLambda().Compile() in GetOuterLambda . I know one solution - it is in the comments to the part of the code. Everything works fine with this, but I need a wrapper as the return value, not an expression subtree (maybe it’s ok to store the internal lambda subtree in LambdaWrapper and compile it later, but the same problem arises).

The error I get is Unhandled Exception: System.InvalidOperationException: variable 'Param' of type 'System.Object' referenced from scope '', but it is not defined .

If I add Param to lock variables in the inner lambda, the code is compiled, but the Param parameter does not have the value assigned in the outer lambda (and that makes sense).

How can this be solved?

+6
source share
2 answers

With the help of Balaz Tihany, I found a solution that works for me exactly the way I need. This is a bit more work because I had to create binders, but in my main project I already had them, so I created dummy attachments for this example.

This is my final decision:

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

Well, since you cannot use Param as a constant value in your internal lambda expression, I suggest you add the lambda parameter to your expression:

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

Then, save the value of the parameter in the LambdaWrapper class and use it later as an argument in the DynamicInvoke call:

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

This works, but the only problem is that it will call WriteLine on Param , which is a ParameterExpression object. To solve this problem, you must dynamically create a wrapper class in your expression tree:

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

Then it will use the assigned value of Param . And since you are not using Param outside GetOuterLambda , now you can use it as a local variable.

EDIT:

Here is my second attempt to solve this problem:

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

This approach compiles this inner lambda when running an external delegate. By doing this, Param will be assigned before the inner lambda is compiled.

+1
source

Source: https://habr.com/ru/post/916174/


All Articles