表达式树 - 在外部 lambda 中编译内部 lambda - 范围解析
本文关键字:lambda 内部 范围 编译 外部 表达式 | 更新日期: 2023-09-27 18:34:46
我正在创建表达式树,在某些情况下,我需要在另一个 lambda 中创建一个 lambda 并将内部 lambda 存储在类中,然后将该类添加到表达式树中。这是我尝试做的简单示例(此代码无法编译(:
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()
行中。我知道一种解决方案 - 它在代码的注释部分中。有了这个,一切正常,但我需要一个包装器作为返回值,而不是表达式子树(可以将内部 lambda 子树存储在 LambdaWrapper 中,稍后编译它,但会出现同样的问题(。
我得到的错误是Unhandled Exception: System.InvalidOperationException: variable 'Param' of type 'System.Object' referenced from scope '', but it is not defined
.
如果我在内部 lambda 中添加 Param
块变量,代码会编译,但 Param 没有在外部 lambda 中分配值(这是有道理的(。
如何解决这个问题?
好吧,由于您不能在内部 lambda 表达式中使用 Param
作为常量值,因此我建议您在表达式中添加一个 lambda 参数:
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
类中,稍后将其用作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
,这是一个参数表达式对象。要解决此问题,您必须在表达式树中动态创建包装类:
//lambdaBody.Add(Expression.Constant(wrapper));
lambdaBody.Add(Expression.New(
typeof(LambdaWrapper).GetConstructor(new[] { typeof(Delegate), typeof(object) }),
Expression.Constant(compiledInnerLambda),
Param)
);
然后它将使用分配的值 Param
。由于您不使用GetOuterLambda
之外的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"))
)
);
}
此方法在运行外部委托时编译此内部 lambda。通过这样做,将在编译内部 lambda 之前分配Param
。
在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");
}
}
}