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

問題はメソッドにGetInnerLambda().Compile()沿っています。GetOuterLambda私は1つの解決策を知っています-それはコードのコメント部分にあります。これで、すべてが正常に機能しますが、式サブツリーではなく、戻り値としてラッパーが必要です(LambdaWrapperに内部ラムダサブツリーを格納し、後でコンパイルしても問題ない場合がありますが、同じ問題が発生します)。

私が得ているエラーはですUnhandled Exception: System.InvalidOperationException: variable 'Param' of type 'System.Object' referenced from scope '', but it is not defined

内部ラムダのブロック変数に追加Paramすると、コードはコンパイルされますが、Paramには外部ラムダに値が割り当てられていません(これは理にかなっています)。

これはどのように解決できますか?

4

2 に答える 2

1

内部ラムダ式で定数値として使用することはできないためParam、ラムダパラメーターを式に追加することをお勧めします。

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

これは機能しますが、唯一の問題は、ParameterExpressionオブジェクトであるWriteLineを呼び出すことです。Paramこれを解決するには、式ツリーにラッパークラスを動的に作成する必要があります。

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

次に、割り当てられた値を使用しますParamParamまた、の外部では使用しないためGetOuterLambda、ローカル変数として使用できるようになりました。

編集:

この問題を解決するための2回目の試みは次のとおりです。

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、内部ラムダがコンパイルされる前に割り当てられます。

于 2012-05-20T07:44:20.353 に答える
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");
        }
    }

}
于 2012-05-21T15:56:24.557 に答える