Expression.LessThan vs LambdaExpression<Func<ParamType,object>>

1.6k views Asked by At

I've got a LambdaExpression which takes one object as a Parameter and ultimately returns an object. For the purposes of testing, here's a Lambda (created longhand to match what i'm really passing in) that returns a DateTime boxed as an object. For the purposes of working this through, the LambdaExpression takes an XmlNode and returns an object. It must return an object, the real return type could be any of: DateTime,bool,int,decimal,XmlDocument [so far] The general idea is that somewhere deep in a parser, this lambda is created and it extracts a value from it's input parameter and returns it typed, but boxed in an object.

     XmlNode node = null;
       ParameterExpression instanceExpression = Expression.Parameter(typeof(DynamicNode), "instance");
       ParameterExpression result = Expression.Parameter(typeof(object), "result");
       LabelTarget blockReturnLabel = Expression.Label(typeof(object));
       BlockExpression block = Expression.Block(
                             typeof(object),
                             new[] {  result },
                             Expression.Assign(result, Expression.Convert(Expression.Constant(DateTime.Now.AddSeconds(-1)), typeof(object))),
                             Expression.Return(blockReturnLabel, result),
                             Expression.Label(blockReturnLabel, Expression.Constant(-2, typeof(object))));
LambdaExpression lax = Expression.Lambda<Func<XmlNode, object>>(block, instanceExpression);

Later in the code, we're evaluating <, <=, >, >=, == and != so we want to compare the result of this LambdaExpression to another Expression

Usually, we can assume the LambdaExpression is on the left of the Expression.LessThan on the right, could be pretty much any expression, but lets assume it's typed. That means that it could be ConstantExpression or similar... but it has a type.

This means that Expression.LessThan [for example] fails because the LambdaExpression when Expression.Invoke is called on it returns an object and the RHS is whatever that type is.

Assuming that the type boxed inside the object returned from the LambdaExpression is actually comparable to the type on the right hand side; e.g.

(object)5 < 6

How do I write an expression that can compare the boxed type to the unboxed type without crashing? I've tried various permutations in linqpad, including trying to write this in normal c#- i.e. no Expressions, just nested if-then-else however I couldn't get that working quite right. Normally, i'd probably write something like this:

/*
int i = 3;
object o = (object)i;
int compare = 4;
*/
DateTime dt = DateTime.Now;
object o = (object)dt;
DateTime compare = DateTime.Now.AddSeconds(1);

bool b = false;
if(o.GetType().IsAssignableFrom(compare.GetType()))
{
    if(o is int)
    {
        b = (int)o < (int)(object)compare;
    }
    if(o is DateTime)
    {
        b = (DateTime)o < (DateTime)(object)compare;
    }
    if(o is decimal)
    {
        b = (decimal)o < (decimal)(object)compare;
    }
}
Console.WriteLine(b);

With this, assuming o and compare are actually the same type and one of them is boxed as an object, we can still perform the < operation...

So I guess my question is, how do I write the above code when I have a LambdaExpression on the left, and Expression on the right [if the two are not the same type, false as a result is better than a crash]

Hope someone can help,

Gareth

2

There are 2 answers

7
Ani On BEST ANSWER

How do I write an expression that can compare the boxed type to the unboxed type without crashing?

You can use the Expression.Unbox method for this: "Creates a UnaryExpression that represents an explicit unboxing."

Let's take your (int)(object)5 < 6 example:

// boxed int
var left = Expression.Constant(5, typeof(object));

// int
var right = Expression.Constant(6, typeof(int));

// More generally, you can use right.Type  instead of typeof(int)
// if its static type is appropriate. 
// Otherwise, you may need to unbox it too.
var unboxedLeft = Expression.Unbox(left, typeof(int));

var lessThanEx = Expression.LessThan(unboxedLeft, right);    
var expression = Expression.Lambda<Func<bool>>(lessThanEx, null);

// True : (int)(object)5 < 6
bool b = expression.Compile()();

So I guess my question is, how do I write the above code when I have a LambdaExpression on the left, and Expression on the right. [if the two are not the same type, false as a result is better > than a crash]

In this case, you can write a conditional-expression that checks if the run-time type of the boxed-object is the same as the type of the right-hand side, and do the unboxing + less-than comparison if they are, or just return false otherwise.

E.g.

// From earlier
var left =  ...
var right = ...
var lessThanEx = ...

var body = Expression.Condition(Expression.TypeEqual(left, right.Type), 
                                lessThanEx, 
                                Expression.Constant(false));

var expression = Expression.Lambda<Func<bool>>(body, null);
2
Gareth Evans On

Combining my original post with your guys answers- this seems to function in LinqPad

XmlNode node = null;
ParameterExpression instanceExpression = Expression.Parameter(typeof(XmlNode), "instance");
ParameterExpression result = Expression.Parameter(typeof(object), "result");
LabelTarget blockReturnLabel = Expression.Label(typeof(object));
BlockExpression block = Expression.Block(
                     typeof(object),
                     new[] {  result },
                     //this would normally be a function invoke
                     Expression.Assign(result, Expression.Convert(Expression.Constant(DateTime.Now.AddSeconds(-1)), typeof(object))),
                     Expression.Return(blockReturnLabel, result),
                     Expression.Label(blockReturnLabel, Expression.Constant(-2, typeof(object))));
LambdaExpression lax = Expression.Lambda<Func<XmlNode, object>>(block, instanceExpression);

var left = Expression.Invoke(lax, instanceExpression);
//false result
//var right = Expression.Constant(5, typeof(int));  
//true result
var right = Expression.Constant(DateTime.Now, typeof(DateTime));

var unboxedLeft = Expression.Unbox(left, right.Type);  
var lessThanEx = Expression.LessThan(unboxedLeft, right);     

var body = Expression.Condition(Expression.TypeEqual(left, right.Type),  lessThanEx,  Expression.Constant(false));  
var expression = Expression.Lambda<Func<XmlNode, bool>>(body, instanceExpression); 

bool b = expression.Compile()(node); 
Console.WriteLine(b);