Pat Niemeyer
Pat Niemeyer

Reputation: 6378

C# construct lambda using Expression.Call doesn't like certain types as params?

For various reasons I'm constructing a C# lambda dynamically using the expression tree facilities. e.g. I can make a Func<string,bool> at runtime as shown in the following snippet.

   public static bool myMethod( object obj ) {  … }

    // Construct a Func<string,bool>
    var myMethod = GetType().GetMethod("myMethod");
    var lambdaParams = new ParameterExpression[] {Expression.Parameter(typeof (string))};
    var callMyMethod = Expression.Call(myMethod, lambdaParams);
    var lambda = Expression.Lambda(typeof(Func<string,bool>), callMyMethod, lambdaParams);
    var del = (Func<string,bool>)lambda.Compile();
    del("foo"); // works

However if I use the same code to try to make a Func<int,bool> or a Func<DateTime,bool> it blows up where indicated with the following strange exception:

    // Construct a Func<DateTime,bool> or perhaps a struct type fails... why?
    var myMethod = GetType().GetMethod("myMethod");
    var lambdaParams = new ParameterExpression[] {Expression.Parameter(typeof (DateTime))};
    var callMyMethod = Expression.Call(myMethod, lambdaParams);  // Blows up here…

System.ArgumentException: Expression of type 'System.DateTime' cannot be used for parameter of type 'System.Object' of method 'Boolean myMethod(System.Object)'

So, string works and List<string> works but int32 does not work nor does DateTime. What is going on? I don't know how the deep internals of C# work but I am guessing it's due to int really being handled as a primitive and maybe DateTime (being a struct) as well...

Any help with this would be greatly appreciated.

thanks, Pat

Upvotes: 2

Views: 2017

Answers (2)

Ani
Ani

Reputation: 113402

As I understand it, Expression.Call doesn't perform auto-boxing of value-type arguments. I'm unable to find any documentation to that effect, but it is mentioned on this forum page.

One workaround would be to explicitly do the boxing conversion in the expression with Expression.TypeAs.

Creates a UnaryExpression that represents an explicit reference or boxing conversion where null is supplied if the conversion fails.

In your case, this should work:

var boxedParams = lambdaParams.Select(p => Expression.TypeAs(p, typeof(object)))
                              .ToArray();

var callMyMethod = Expression.Call(myMethod, boxedParams);

(You don't need the fancy lambdas if there's only one parameter)

Depending on the real usage, you may have to check if the boxing conversion is necessary depending on whether the type(s) in question is(are) value-type(s).

Upvotes: 2

user47589
user47589

Reputation:

Check this out: you have to box the DateTime, since DateTime isnt' a reference type!

// Construct a Func<DateTime,bool>
var myMethod = typeof(Program).GetMethod("myMethod");
var param = Expression.Parameter(typeof(DateTime));
var boxy = Expression.TypeAs(param, typeof(object));

var callMyMethod = Expression.Call(myMethod, boxy);
var lambda = Expression.Lambda(typeof(Func<DateTime, bool>), callMyMethod, new ParameterExpression[] { param });
var del = (Func<DateTime,bool>)lambda.Compile();
del(DateTime.Now); // works

Upvotes: 1

Related Questions