4

When I have this,

public static object Create()
{
    return new object();
}

this works:

var m = typeof(Class).GetMethod("Create");
var e = Expression.Call(m);
Func<object> f = Expression.Lambda<Func<object>>(e).Compile();

But when I have this,

public static object Create(Type t)
{
    return new object();
}

this fails:

var m = typeof(Class).GetMethod("Create");
var e = Expression.Call(m, Expression.Parameter(typeof(Type)));
var t = Expression.Parameter(typeof(Foo));
Func<object> f = Expression.Lambda<Func<object>>(e, t).Compile();

I get An unhandled exception of type 'System.ArgumentException' occurred in System.Core.dll. Additional information: Incorrect number of parameters supplied for lambda declaration. The parameter t is just expression for a dummy type Foo. I think that's irrelevant. Where have I gone wrong here?

nawfal
  • 70,104
  • 56
  • 326
  • 368

1 Answers1

14

The problem is that you've said you want to use a parameter - but then you're not actually providing anywhere to specify it. You were creating two ParameterExpressions of different types, and then trying to convert the result into a Func<object> - which doesn't have any parameters at all. You want something like:

var m = typeof(Class).GetMethod("Create");
var p = Expression.Parameter(typeof(Type), "p");
var e = Expression.Call(m, p);
Func<Type, object> f = Expression.Lambda<Func<Type, object>>(e, p).Compile();

Note that the same ParameterExpression is used in the argument list for both theExpression.Call method and the Expression.Lambda methods.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    I don't understand this - "Also note that I've explicitly created an array for the arguments to Expression.Call to avoid trying to use p as if it were the target of an instance method call." It seems to work fine with just p, instead of an array ... can you expand on this a bit? – Richard Anthony Hein Apr 23 '13 at 14:21
  • 1
    Also, using var e = Expression.Call(m, null, p) results in an "ArgumentNullException: Value cannot be null. Parameter name: arg0". – Richard Anthony Hein Apr 23 '13 at 14:24
  • @RichardHein: If you use `Expression.Call(m, p)` I'd expect that to be as if we were trying to call `p.Create()` effectively. Apparently not though... I'm not sure why, but I'll edit. – Jon Skeet Apr 23 '13 at 14:39
  • 2
    @RichardHein: Ah, got it. For instance methods the target of the call is the first argument, not the second. Doh. – Jon Skeet Apr 23 '13 at 14:40
  • @JonSkeet well written, accepted; somehow overlooked it. Can you tell me what is really the significance of `p` going into both `Expression.Call` and `Expression.Lambda`? – nawfal Apr 23 '13 at 14:41
  • 2
    @nawfal: Yes - it's binding the two together. Imagine you were writing a lambda expression - you'd use `p => Create(p)` and the `p` refers to the same variable (the parameter) in both places. You might *expect* to be able to just create two `ParameterExpression` objects using the same name and bind that way, but that doesn't work. – Jon Skeet Apr 23 '13 at 14:43
  • @JonSkeet thanks, I understand, just new to this dynamic building thing :) – nawfal Apr 23 '13 at 14:44
  • 1
    Ah yes, instance vs. statics ... good to highlight that. – Richard Anthony Hein Apr 23 '13 at 15:09
  • 1
    Can't thank you enough, @JonSkeet! – nkanani Jul 13 '15 at 12:30