Постановка задачи
Работая с технологией Linq, я недавно уперся в следующую проблему. В рамках моей задачи, мне было необходимо выражение вида
Expression<Func<T1,bool>>, которое я должен был передать в Linq-запрос. При этом, извне мне передали выражение вида
Expression<Func<T2,bool>>. Кроме того, у меня есть преобразование вида
Expression<Func<T1,T2>>. Если бы мы имели дело с простыми делегатами типа Func, то все решилось бы последовательным вызовом двух функций: f = x => f1( f2( x ) );. Однако, с выражениями, мы такой финт сделать не можем, а приведения выражений к делегатам методом Compile(), создадут нам анонимные классы и последующая свертка сведется, соответственно, к вызову методов этих классов. А это значит, что результирующее выражение уже можно будет использовать только, как делегат - полноценного дерева выражений (к примеру, для построения SQL запроса) уже не будет.
Решение
Что бы сохранить нормальную структуру наших выражений, будем использовать класс
ExpressionVisitor, с помощью которого обойдем дерево первого выражения и вместо переменной типа T2 подставим выражение для T1=>T2. Для этого напишем класс-наследник
ExpressionVisitor:
public class ReplacementVisitor : ExpressionVisitor
{
readonly Expression fromExpr;
readonly Expression toExpr;
public ReplacementVisitor( Expression oldExpr, Expression newExpr )
{
fromExpr = oldExpr;
toExpr = newExpr;
}
public override Expression Visit( Expression node )
{
if( node == fromExpr )
return toExpr;
return base.Visit( node );
}
}
Используя этот класс, создадим статическую функцию, которая собственно и будет решать нашу задачу
public static class ExpressionHelper
{
public static Expression<Func<TSrc, TRes>> Combine<TSrc, TMes, TRes>( this Expression<Func<TSrc, TMes>> firstExpr, Expression<Func<TMes, TRes>> secondExpr )
{
var param = Expression.Parameter( typeof( TSrc ), "x" );
var visitor = new ReplacementVisitor( firstExpr.Parameters[0], param );
var newParentBody = visitor.Visit( firstExpr.Body );
visitor = new ReplacementVisitor( secondExpr.Parameters[0], newParentBody );
var body = visitor.Visit( secondExpr.Body );
return Expression.Lambda<Func<TSrc, TRes>>( body, param );
}
}
Осталось только сказать, что класс ExpressionVisitor появился в .Net Framework 4.0. Если же вы используете более ранние версии .Net, то данный класс можно взять с сайта Microsoft. Например, отсюда
http://msdn.microsoft.com/ru-ru/library/bb882521(v=vs.90).aspx.
Комментариев нет:
Отправить комментарий