вторник, 17 сентября 2013 г.

Слияние двух выражений (Linq.Expression)

Постановка задачи

Работая с технологией 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.

Комментариев нет:

Отправить комментарий