Tworzę drzewo wyrażeń i jest sytuacja, w której potrzebuję utworzyć jedną lambdę w innej klasie lambda i zapisać wewnętrzną w klasie i dodać tę klasę w drzewie wyrażeń. Jest to prosty przykład tego, co próbuję zrobić (ten kod nie skompilować):Drzewo wyrażeń - skompiluj wewnętrzną lambdę w zewnętrznej rozdzielczości 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();
}
}
}
Problem jest w GetInnerLambda().Compile()
linii GetOuterLambda
metody. Jestem świadomy jednego rozwiązania - jest to w komentarzu części kodu. Dzięki temu wszystko działa dobrze, ale potrzebuję otoki jako wartości zwracanej, a nie poddrzewa wyrażeń (może być ok, aby przechowywać wewnętrzne poddrzewo lambdy w LambdaWrapper i skompilować je później, ale wystąpi ten sam problem).
Błąd otrzymuję: Unhandled Exception: System.InvalidOperationException: variable 'Param' of type 'System.Object' referenced from scope '', but it is not defined
.
Jeśli dodaję Param
do blokowania zmiennych w wewnętrznej lambda, kompiluje kod, ale Param nie ma wartości przypisanej w zewnętrznej lambda (i to ma sens).
Jak można to rozwiązać?
Dzięki za odpowiedź.To, co nie podoba mi się w tym podejściu, to fakt, że te lambdy są rzeczywistymi funkcjami i mogą mieć swoje parametry (nie uwzględniłem tej części, ponieważ nie mam z tym problemu), a Param jest nie tylko zmienna Potrzebuję dostępu (może być ich dużo), więc nie sądzę, że dodanie sztucznych parametrów do rozwiązania scopingu jest bardzo eleganckim rozwiązaniem. –
Zaktualizowana odpowiedź może dla mnie zadziałać, ale będę musiał sprawdzić, kiedy wrócę do mojego komputera roboczego. Dzięki ... –
Sprawdziłem, czy to działa dla mnie. Prawie :). Poszedłem o krok dalej i stworzyłem DynamicExpression do tworzenia instancji LambdaWrapper. Musiałem stworzyć spoiwa, więc ta dusza wymaga więcej pracy, ale i tak miałem je już w moim głównym projekcie. Dziękuję za zainteresowanie, które okazałeś przy rozwiązywaniu tego problemu :) –