I have this little bit of code where I take a ParameterExpression
array of strings and convert a particular index to a target type. I do this either by calling Parse
(if the type is primitive) or by attempting a raw conversion (hopefully to a string or implicit string).
static Expression ParseOrConvert(ParameterExpression strArray, ConstantExpression index, ParameterInfo paramInfo)
{
Type paramType = paramInfo.ParameterType;
Expression paramValue = Expression.ArrayIndex(strArray, index);
if (paramType.IsPrimitive) {
MethodInfo parseMethod = paramType.GetMethod("Parse", new[] { typeof(string) });
// Fetch Int32.Parse(), etc.
// Parse paramValue (a string) to target type
paramValue = Expression.Call(parseMethod, paramValue);
}
else {
// Else attempt a raw conversion
paramValue = Expression.Convert(paramValue, paramType);
}
return paramValue;
}
This works, but I'm trying to rewrite the conditional as such.
paramValue = Expression.Condition(
Expression.Constant(paramType.IsPrimitive),
Expression.Call(parseMethod, paramValue),
Expression.Convert(paramValue, paramType)
);
This always results in System.InvalidOperationException
, presumably because it attempts both conversions. I find the second style more intuitive to write in so this is unfortunate.
Can I write this in a way that defers evaluation to when the values are actually needed?
Expressions represent code as data, the true and false branches are not being evaluated here; it is not "attempting both conversions". Instead, it is trying to build an Expression Tree that represents each conversion. The condition however, being a Constant
, is being baked into the conditional eagerly based on the type.
You are building an expression with the following structure:
var result = true // <`true` or `false` based on the type T>
? T.Parse(val)
: (T) val;
When T
is int
(and thus the "Test" is the constant true
) this does not compile because there is no valid cast from string
to int
, even though at runtime it would always evaluate/execute int.Parse(val)
.
When T
is Foo
, this would compile when Foo
has both a static Parse(string val)
method, and an explicit cast operator
public class Foo
{
public static Foo Parse(string fooStr)
{
return default(Foo);
}
public static explicit operator Foo(string fooStr)
{
return default(Foo);
}
}
Even though it would only ever execute the explicit cast operator because Foo
is not primitive.
Your original code actually already builds an Expression that will use the "correct" conversion strategy based on T
without trying to compile/evaluate the other one. If this isn't working for you already, I suspect it's because the types involved don't have an explicit cast defined from string
.
As an aside, I would discourage reusing paramValue
as both the original (unconverted) and converted value, it makes debugging much harder than it needs to be, among other things.