tags:

views:

96

answers:

1

Another Linq question =)

So I have a certain interface that I can code against and an expression that has this signature

 Expression<Func<TInterface, bool>>

At some point I will need to use that expression but it needs to look like this

 Expression<Func<TImplementaion, bool>>

I ve tried this

Expression<Func<TImplementation, bool>> expression = x => myExpression.Compile().Invoke(x);

And Although this compiles the expression gets lost in translatiion Any ideas? Thanks

+2  A: 

AFAIK The BCL has very limited support for working with Expressions. I'm afraid that you're going to have to rewrite the expression yourself to change the method parameter type.

It's not hard, but not easy either. Basically, you will clone every node of the Expression (it's a tree) but set the root node's data type to your Func<TImplementation, bool>.

I would look for a different design that accomplishes the same goal but doesn't have this casting requirement - plowing through Expressions is not fun.

Update I've implemented a function that does what you want. I call it CastParam:

public static Expression<Func<TOut, bool>> CastParam<TIn, TOut>(this Expression<Func<TIn, bool>> inExpr) {
 if (inExpr.NodeType == ExpressionType.Lambda &&
     inExpr.Parameters.Count > 0) {

  var inP = inExpr.Parameters[0];
  var outP = Expression.Parameter(typeof(TOut), inP.Name);

  var outBody = inExpr.Body.ConvertAll(
   expr => (expr is ParameterExpression) ? outP : expr);       
  return Expression.Lambda<Func<TOut,bool>>(
   outBody,
   new ParameterExpression[] { outP });
 }
 else {
  throw new NotSupportedException();
 }
}

All it does is rewrite the expression substituting the old ParamaterType with the new type. Here is my little test:

class TInterface { public int IntVal; }
class TImplementation : TInterface { public int ImplVal; }

void Run ()
{
 Expression<Func<TInterface, bool>> intExpr = (i => i.IntVal == 42);
 Expression<Func<TImplementation, bool>> implExpr = intExpr.CastParam<TInterface, TImplementation> ();

 Console.WriteLine ("{0} --> {1}", intExpr, implExpr);

 var c = implExpr.Compile ();

 Console.WriteLine (c.Invoke (new TImplementation { IntVal = 41, ImplVal = 42 }));
 Console.WriteLine (c.Invoke (new TImplementation { IntVal = 42, ImplVal = 41 }));
}

As expected, it prints:

False
True

The code relies on an Expression rewriter that I wrote (rewrites expression trees from the bottom up):

public static Expression Rewrite(this Expression exp, Func<Expression, Expression> c) {
    Expression clone = null;
    switch (exp.NodeType) {
     case ExpressionType.Equal: {
      var x = exp as BinaryExpression;
      clone = Expression.Equal(Rewrite(x.Left,c), Rewrite(x.Right,c), x.IsLiftedToNull, x.Method);
      } break;
     case ExpressionType.MemberAccess: {
      var x = exp as MemberExpression;
      clone = Expression.MakeMemberAccess(Rewrite(x.Expression,c), x.Member);
      } break;
     case ExpressionType.Constant: {
      var x = exp as ConstantExpression;
      clone = Expression.Constant(x.Value);
      } break;
     case ExpressionType.Parameter: {
      var x = exp as ParameterExpression;
      clone = Expression.Parameter(x.Type, x.Name);
      } break;
     default:
      throw new NotImplementedException(exp.NodeType.ToString());
    }
    return c(clone);
}

The rewriter is obviously incomplete and you'll need to finish it off.

Frank Krueger
thanks for the quick answer, I was afraid of that :(. I ll live the question live for another while in case someone has some alternative ideas
Miau
Absolutely do. Also you can eventually set a bounty on it and maybe someone will write the code for you. :-)
Frank Krueger
Thanks for this I did something slightly different momentarily ( I have a private class that implements the interface and I recreate it from the setted properties. Its just a few lines of codes but I m not terribly happy with it so I ll have to revisit this after today ( small release today)
Miau
ConvertAll doesnt exist, am i missing something ?!
Stacker