0

I am trying to use C# object initializer syntax with LINQ Expressions trees and I used type conversions (i.e. Expression.Convert) so I shouldn't get invalid cast exceptions for simple type conversions (i.e. int to double) but I still get invalid cast exceptions.

Error:

Unhandled exception. System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.Double'.
   at lambda_method1(Closure, Dictionary`2)
   at Program.Main(String[] args) in 

Lambda expression I am generating dynamically:

Dictionary<string, object> $var0) => new Foo() {
    Name = (string)$var0["Name"],
    Salary = (double)$var0["Salary"]
}

Code:

internal class Program
{
    public static void Main(string[] args)
    {
        var foo = (Foo)InstantiateAndInitializeType(typeof(Foo))(new Dictionary<string, object>
        {
            ["Name"] = "foobar",
            ["Age"] = 12,
            ["Salary"] = 20
        });
        
        Console.WriteLine(JsonConvert.SerializeObject(foo, Formatting.Indented));
    }

    public static Func<Dictionary<string, object>, object> InstantiateAndInitializeType(Type type)
    {
        var dictArg = Expression.Parameter(typeof(Dictionary<string, object>));
        
        var body = Expression.MemberInit(Expression.New(typeof(Foo)), typeof(Foo)
            .GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.SetProperty)
            .Where(x => x.GetCustomAttribute<RequiredMemberAttribute>() != null)
            .Select(x => Expression.Bind(x, Expression.Convert(Expression.Property(dictArg, "Item", Expression.Constant(x.Name)), x.PropertyType))));

        var lambdaExpr = Expression.Lambda<Func<Dictionary<string, object>, object>>(body, dictArg);
        var func = lambdaExpr.Compile();
        return func;
    }
}

class Foo
{
    public required string Name { get; set; }
    
    
    public double Age { get; set; }
    
    
    public required double Salary { get; set; }
}
4
  • I used Expression.Convert for this exact reason. Why is it not working? Commented Jul 11, 2023 at 21:36
  • Your issue is implicit in the details of the error message: Unable to cast object of type 'System.Int32' to type 'System.Double'. You get the same error if you do double d = (double)(object)32; Commented Jul 11, 2023 at 21:51
  • I thought Expression.Convert is equivalent to Convert.ChangeType. So how to fix this code? Commented Jul 11, 2023 at 21:56
  • It is not. Expression.Convert is a typecast and the C# documentation for Explicit Conversions that are Unboxing Conversions (from reference type (object) to value type (double)) states that only when the reference is to a boxed value of the correct type. Since the value is actually an Int32, the conversion fails at runtime. Commented Jul 11, 2023 at 23:19

3 Answers 3

1

Since you don't know the "real" type of the object in each Dictionary entry, you must use Convert.ChangeType to convert to the desired target type, then use a cast to unbox the object return when you want a value type.

public static Func<Dictionary<string, object>, object> InstantiateAndInitializeType(Type type) {
    // Convert.ChangeType
    var ctMI = typeof(Convert).GetMethod("ChangeType", new[] { typeof(Object), typeof(Type) });
    // could put this in a static outside the method since it won't change at runtime

    // (Dictionary<string,object> p)
    var dictParm = Expression.Parameter(typeof(Dictionary<string, object>), "p");

    // new Foo()
    var newFooExpr = Expression.New(typeof(Foo));

    // { {prop} = ({prop type})Convert.ChangeType(p[{prop name}], {prop type}), ... }
    var propBindExprs = typeof(Foo)
        .GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.SetProperty)
        .Where(propInfo => propInfo.GetCustomAttribute<RequiredMemberAttribute>() != null)
        .Select(propInfo =>
            // {prop} = ({prop type})Convert.ChangeType(p[{prop name}], typeof({prop type}))
            Expression.Bind(
                propInfo, // {prop}
                // ({prop type})
                Expression.Convert(
                    // Convert.ChangeType(p[{prop name}], typeof({prop type}))
                    Expression.Call(null,
                                    ctMI, // Convert.ChangeType
                                    // p[{prop name}]
                                    Expression.Property(dictParm, "Item", Expression.Constant(propInfo.Name)),
                                    // typeof({prop type})
                                    Expression.Constant(propInfo.PropertyType)),
                    // {prop type}
                    propInfo.PropertyType)));

    // new Foo() { {prop} = ({prop type})Convert.ChangeType(p[{prop name}], {prop type}), ... }
    var body = Expression.MemberInit(Expression.New(typeof(Foo)), propBindExprs);
    // (Dictionary<string,object> p) => new Foo() { {prop} = ({prop type})Convert.ChangeType(p[{prop name}], {prop type}), ... }
    var lambdaExpr = Expression.Lambda<Func<Dictionary<string, object>, object>>(body, dictParm);

    var func = lambdaExpr.Compile();
    return func;
}

This isn't terribly efficient if you are building and compiling a lot of init functions, but if you re-use a standard one that expects a given Dictionary, it might be worthwhile to add some optimizations to remove the call to Convert.ChangeType for when the actual type in the Dictionary can be cast to the property type or matches the property type exactly (e.g. String).

Sign up to request clarification or add additional context in comments.

Comments

1

A simplified reproduction of the issue you're experiencing looks like this:

object salary = 20;
double salary2 = (double)salary; // InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.Double'.

Either change your original value to be of the right type:

object salary = 20d;

Or change the code to translate the value into the type that it actually is set to:

double salary2 = (double)(int)salary;

But if you don't know what type it will be ahead of time, you can use a more flexible conversion technique. :

double salary2 = (double)Convert.ChangeType(salary, typeof(double));

This will accommodate a wide variety of inputs, including strings ("20"), but it is not quite as fast as the other options.

It looks like that last approach is the one you landed on, but I wanted to call out that there are other options, and help to explain that the root cause is not in the creation of the expression, but rather in the code you're trying to replicate in the first place.

Comments

0

I had to use Convert.ChangeType

    public static Func<Dictionary<string, object>, object> InstantiateAndInitializeType(Type type)
    {
        var dictArg = Expression.Parameter(typeof(Dictionary<string, object>));
        
        var body = Expression.MemberInit(Expression.New(typeof(Foo)), typeof(Foo)
            .GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.SetProperty)
            .Where(x => x.GetCustomAttribute<RequiredMemberAttribute>() != null)
            .Select(x =>
            {
                return Expression.Bind(x, Expression.Convert(Expression.Call(
                        null,
                        typeof(Convert).GetMethod("ChangeType", new[] { typeof(object), typeof(Type) }),
                        Expression.Property(dictArg, "Item", Expression.Constant(x.Name)),
                        Expression.Constant(x.PropertyType))
                    , x.PropertyType));
            }));

        var lambdaExpr = Expression.Lambda<Func<Dictionary<string, object>, object>>(body, dictArg);
        Console.WriteLine(lambdaExpr.ToString("C#"));
        var func = lambdaExpr.Compile();
        return func;
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.