1
votes

I'm trying to create an expression to convert some numeric values. Here is what I have tried:

public object ConvertValue(object value, Type targetType) {
    var parameter = Expression.Parameter(typeof(object), "p"); // "p"
    var convert = Expression.Convert(parameter, targetType); // Convert(p, Int64)
    var targetConvert = Expression.Convert(convert, typeof(object)); // Convert(Convert(p, Int64), Object)
    var lambda = Expression.Lambda<Func<object,object>>(targetConvert, parameter); // p => Convert(Convert(p, Int64), Object)
    var method = lambda.Compile();
    var result = method(value); // HERE I GET THE ERROR!
    return result;
}

But when I call it, as this simple test:

[Fact]
public void TestConvert() {
    var result = ConvertValue(23, typeof(long));
    Assert.Equal(typeof(long), result.GetType());
}

I get the error:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.Int64'.
at lambda_method(Closure , Object ) bla bla bla...

Any idea what's happening here and what Int32 cannot be cast to Int64? Thanks in advance.

2
Technically, under the hood, both are different types and cannot be casted, just as you cannot cast string to int. What usually happens is that what looks like a cast in C# can be any of the following things: A type-cast, an invocation of an explicit conversion operator (which is what happens with numeric conversions like "casting" int to long), boxing a value type, or unboxing a value type. It can be a bit confusing that all those things share the same syntax. - Joey
@Joey thanks to the description. I forgot that. Have you any idea how to fix the expression? - amiry jd
How is this intended to be different from the built-in Convert.ChangeType method? - D Stanley

2 Answers

0
votes

The following takes advantage of Convert.ChangeType to provide more flexibility with the conversions.

The code comments document what is being done.

public object ConvertValue(object value, Type targetType){
    var valueType = value.GetType();
    // Func<TValue,TTarget>
    var delegateType = typeof(Func<,>).MakeGenericType(valueType, targetType);
    var convert = typeof(Convert).GetMethod("ChangeType", new[] { typeof(object), typeof(Type) });
    // TValue p
    var parameter = Expression.Parameter(valueType, "p");
    // Convert.ChangeType(Convert(p), targetType);
    var changeType = Expression.Call(convert, Expression.Convert(parameter, typeof(object)), Expression.Constant(targetType));
    // (TTarget)Convert.ChangeType(Convert(p), targetType);
    var body = Expression.Convert(changeType, targetType);
    //Func<TValue,TTarget> = TValue p => (TTarget)Convert.ChangeType(Convert(p), targetType);
    var lambda = Expression.Lambda(delegateType, body, parameter);
    var method = lambda.Compile();
    var result = method.DynamicInvoke(value);
    return result;
}

The following basic tests all passed when exercised

[TestMethod]
public void Should_Convert_Int_To_Long() {
    var expected = typeof(long);
    var actual = ConvertValue(23, expected);
    Assert.AreEqual(expected, actual.GetType());
}

[TestMethod]
public void Should_Convert_Long_To_Int() {
    var expected = typeof(int);
    var actual = ConvertValue((long)23, expected);
    Assert.AreEqual(expected, actual.GetType());
}

[TestMethod]
public void Should_Convert_String_To_Long() {
    var expected = typeof(long);
    var actual = ConvertValue("23", expected);
    Assert.AreEqual(expected, actual.GetType());
}

[TestMethod]
public void Should_Convert_String_To_Int() {
    var expected = typeof(int);
    var actual = ConvertValue("23", expected);
    Assert.AreEqual(expected, actual.GetType());
}
0
votes

What you try to do is (int64)(object)23 and it won't work because 23 is of type int32. You can pass 23L literal and it'll work:

ConvertValue(23L, typeof(long));

To make your code work, you need to call convert from int32 to int64 after unboxing:

public static object ConvertValue(object value, Type targetType) {
    var parameter = Expression.Parameter(typeof(object), "p"); // "p"
    var convert = Expression.Convert(Expression.Convert(parameter, value.GetType()), targetType); // Convert(p, Int64)
    var targetConvert = Expression.Convert(convert, typeof(object)); // Convert(Convert(p, Int64), Object)
    var lambda = Expression.Lambda<Func<object,object>>(targetConvert, parameter); // p => Convert(Convert(p, Int64), Object)
    var method = lambda.Compile();
    var result = method(value);
    return result;
}