Expression.Convert:类型为'System.Int64'的对象无法转换为类型'System.Int32'

7
我昨天在这里提出了一个与读取匿名对象的属性并将其写入类的私有字段相关的问题。问题已经解决。以下是简短的故事:
我有一些以json格式存储的数据。我将它们反序列化为ExpandoObject,并将它们作为IDictionary<string, object>传递给方法。除了Int32属性之外,它都可以正常工作。似乎它们变成了Int64,但我不知道原因。
以下是该方法:
    private Func<IDictionary<string, object>, dynamic> MakeCreator(
        Type type, Expression ctor,
        IEnumerable<PropertyToFieldMapper> maps) {

        var list = new List<Expression>();
        var vList = new List<ParameterExpression>();

        // creating new target
        var targetVariable = Expression.Variable(type, "targetVariable");
        vList.Add(targetVariable);
        list.Add(Expression.Assign(targetVariable, Expression.Convert(ctor, type)));

        // accessing source
        var sourceType = typeof(IDictionary<string, object>);
        var sourceParameter = Expression.Parameter(sourceType, "sourceParameter");

        // calling source ContainsKey(string) method
        var containsKeyMethodInfo = sourceType.GetMethod("ContainsKey", new[] { typeof(string) });

        var accessSourceIndexerProp = sourceType.GetProperty("Item");
        var accessSourceIndexerInfo = accessSourceIndexerProp.GetGetMethod();

        // itrate over writers and add their Call to block
        var containsKeyMethodArgument = Expression.Variable(typeof(string), "containsKeyMethodArgument");
        vList.Add(containsKeyMethodArgument);
        foreach (var map in maps) {
            list.Add(Expression.Assign(containsKeyMethodArgument, Expression.Constant(map.Property.Name)));
            var containsKeyMethodCall = Expression.Call(sourceParameter, containsKeyMethodInfo,
                                                        new Expression[] { containsKeyMethodArgument });

            // creating writer
            var sourceValue = Expression.Call(sourceParameter, accessSourceIndexerInfo,
                                              new Expression[] { containsKeyMethodArgument });
            var setterInfo = map.Field.GetType().GetMethod("SetValue", new[] { typeof(object), typeof(object) });
            var setterCall = Expression.Call(Expression.Constant(map.Field), setterInfo,
                new Expression[] {
                                     Expression.Convert(targetVariable, typeof(object)),
                                     Expression.Convert(sourceValue, typeof(object))
                                 });
            Console.WriteLine(Expression.Lambda(setterCall));
            list.Add(Expression.IfThen(containsKeyMethodCall, setterCall));
        }
        list.Add(targetVariable);

        var block = Expression.Block(vList, list);

        var lambda = Expression.Lambda<Func<IDictionary<string, object>, dynamic>>(
            block, new[] { sourceParameter }
            );

        return lambda.Compile();
    }

如果我们有这个:
public class Person {
    public int Age { get; set; }
    public string Name { get; set; }
}

创建类,并使用该对象

var data = new { Name = "Amiry", Age = 20 };

使用以上方法初始化Person实例时,会出现以下错误:

无法将类型为“System.Int64”的对象转换为类型“System.Int32”。

但如果我们将Age属性改为:

public long Age { get; set; }

一切看起来都很好,方法运行得很完美。我完全困惑于为什么会发生这种情况。你有任何想法吗?


你是通过反射设置属性吗? - Mukesh Sakre
从检查您的Dictionary/sourceParameter开始。它里面有long吗?如果有,那么这个映射代码与问题无关。 - Andrey Shchekin
不是属性,私有字段。我在代码中通过setterInfo进行设置。完整的解决方案在这里。如果你想的话,请看一下。 - amiry jd
你是在指映射代码吗?它在映射代码中将是 int,因为它基于字段类型。但在实际字典中可能是 long - Andrey Shchekin
1
@Javad_Amiry 添加了一个答案。 - Andrey Shchekin
显示剩余2条评论
2个回答

5
表达式是正确的。问题在于Json.NET。它将所有数值(在匿名转换中)转换为Int64。因此,我只需要一个自定义转换器:
public class JsonIntegerConverter : JsonConverter {

    public override bool CanConvert(Type objectType) {
        return objectType == typeof(IDictionary<string, object>);
    }

    public override bool CanWrite {
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
        var result = new Dictionary<string, object>();
        reader.Read();

        while (reader.TokenType == JsonToken.PropertyName) {
            var propertyName = (string)reader.Value;
            reader.Read();
            object value;
            if (reader.TokenType == JsonToken.Integer) {
                var temp = Convert.ToInt64(reader.Value);
                if (temp <= Byte.MaxValue && temp >= Byte.MinValue)
                    value = Convert.ToByte(reader.Value);
                else if (temp >= Int16.MinValue && temp <= Int16.MaxValue)
                    value = Convert.ToInt16(reader.Value);
                else if (temp >= Int32.MinValue && temp <= Int32.MaxValue)
                    value = Convert.ToInt32(reader.Value);
                else
                    value = temp;
            } else
                value = serializer.Deserialize(reader);
            result.Add(propertyName, value);
            reader.Read();
        }

        return result;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
        throw new NotSupportedException();
    }
}

这是一种具体实现方法,当然可以更进一步地扩展和优化,但它已经解决了我的当前问题。


这对我非常有效。我建议做一个更改,将: return objectType == typeof(IDictionary<string, object>); 替换为 return typeof(IDictionary<string, object>).IsAssignableFrom(objectType);这样它就适用于任何字典类型。 - Mike
1
很遗憾,这个解决方案不能递归反序列化数据。在处理整数后,我检查 reader.TokenType == JsonToken.StartObject,然后使用 value = serializer.Deserialize<IDictionary<string, object>>(reader); 递归反序列化它。 - AshleyS

4

因评论讨论,您的输入字典包含长整型

最简单的解决方法是在SetValue之前添加Convert.ChangeType
(传递sourceValueConstant(map.Field.FieldType))
然而,这可能会造成意想不到的后果,使得字符串 -> 整型转换。

另一种方法是添加自己的ConvertType方法,在其中决定如何转换类型。


对这个想法点赞。但是,它并不像你说的那么简单 :D 我正在处理一个表达式树,并且我需要创建一些表达式来调用 Convert.ChangeType :( 看起来创建一个自定义的 JsonConvertor 更容易些。无论如何,感谢你的想法。干杯。 - amiry jd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接