Reputation: 39277
I have the method below which converts a (non-static) MethodInfo
into a compiled Expression (Func
) which I can then call.
This works great: I can call it with a method expecting both reference objects and value types.
BUT unlike the original method where I could call a method that had a parameter expecting a double
and pass it an int
this compiled expression doesn't support that and throws an InvalidCastException
.
How do I modify this to support the same type of implicit casting that happens during a normal method call?
Bonus question: should the instanceExp use the DeclaringType
or the ReflectedType
from the MethodInfo
?
public Func<object, object[], object> Create(MethodInfo methodInfo)
{
var methodParams = methodInfo.GetParameters();
var arrayParameter = Expression.Parameter(typeof(object[]), "array");
var arguments =
methodParams.Select((p, i) => Expression.Convert(
Expression.ArrayAccess(arrayParameter, Expression.Constant(i)), p.ParameterType))
.Cast<Expression>()
.ToList();
var instanceParameter = Expression.Parameter(typeof(object), "controller");
var instanceExp = Expression.Convert(instanceParameter, methodInfo.DeclaringType);
var callExpression = Expression.Call(instanceExp, methodInfo, arguments);
var bodyExpression = Expression.Convert(callExpression, typeof(object));
return Expression.Lambda<Func<object, object[], object>>(
bodyExpression, instanceParameter, arrayParameter)
.Compile();
}
--- EDIT
The working solution is:
var changeTypeMethod = typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(TypeCode) });
var arguments =
methodParams.Select((p, i) =>
!typeof(IConvertible).IsAssignableFrom(p.ParameterType)
// If NOT IConvertible, don't try to convert it
? (Expression)Expression.Convert(
Expression.ArrayAccess(arrayParameter, Expression.Constant(i)), p.ParameterType)
:
// Otherwise add an explicit conversion to the correct type to handle int <--> double etc.
(Expression)Expression.Convert(
Expression.Call(changeTypeMethod,
Expression.ArrayAccess(arrayParameter, Expression.Constant(i)),
Expression.Constant(Type.GetTypeCode(p.ParameterType))),
p.ParameterType)
)
.ToList();
Upvotes: 4
Views: 970
Reputation: 726479
The problem is the same as in this piece of C# code:
object a = 123;
double b = (double)a; // InvalidCastException
The reason is that a
is an object
, so in order to make it a double
the cast must unwrap it, and then transform an int
to double
. The language allows the cast to do only one thing - it will either unwrap or transform, but not both. You need to tell the compiler how to do this cast explicitly by telling it that there is an int
wrapped inside the object
:
double b = (double)((int)a); // Works
If you could do the same thing in your LINQ expression, your compiled expression will work as well. However, you may not know the actual type of the parameter at the time you generate your expression, so you may want to go for a different strategy - wire in a call to Convert.ChangeType
method, which can unwrap and cast at the same time.
Upvotes: 5