【问题标题】:Expression.Convert: Object of type 'System.Int64' cannot be converted to type 'System.Int32'Expression.Convert:“System.Int64”类型的对象无法转换为“System.Int32”类型
【发布时间】:2013-05-22 05:00:03
【问题描述】:

我昨天问了一个问题here,关于从匿名对象中读取属性并将它们写入类的私有字段。问题解决了。这是一个短篇故事:

我有一些 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; }

一切看起来都很好,方法也很完美。我完全不明白为什么会发生这种情况。你有什么想法吗?

【问题讨论】:

  • 你是不是在反思自己?
  • 首先检查您的Dictionary/sourceParameter。里面有long吗?如果是,则此映射代码与问题无关。
  • 不是属性,私有字段。我在代码中通过setterInfo 设置它们。完整的解决方案位于here。如果你愿意,请看一下。
  • @AndreyShchekin 不,它没有。在将其表达式添加到setterCall之前是Int32
  • @Javad_Amiry 添加了答案。

标签: c# reflection lambda expression


【解决方案1】:

表达式是正确的。问题是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&lt;string, object&gt;); 替换为 return typeof(IDictionary&lt;string, object&gt;).IsAssignableFrom(objectType); 这样它适用于任何字典类型。
  • 不幸的是,此解决方案不会递归地反序列化数据。处理完整数后,我检查是否reader.TokenType == JsonToken.StartObject,然后用value = serializer.Deserialize&lt;IDictionary&lt;string, object&gt;&gt;(reader);递归反序列化它
【解决方案2】:

所以您的输入 Dictionary 包含 long(基于 cmets 中的讨论)。

最简单的解决方法是在SetValue 之前添加Convert.ChangeType
(传入sourceValueConstant(map.Field.FieldType)
但是,这可能会导致允许string -&gt; int 转换的意外后果。

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

【讨论】:

  • +1 到想法。但这并不像你说的那么简单 :D 我在表达式树中,我应该创建一些表达式来调用 Convert.ChangeType :( 似乎创建自定义 JsonConvertor 更容易。但是,谢谢你的想法. 干杯。
猜你喜欢
  • 2017-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-10
相关资源
最近更新 更多