Hello World
Spiga

当类型转换表达式遇上自定义转换操作

2010-10-15 13:50 by 老赵, 1682 visits

之前我提到说System.Json是一个十分不好用的类库,其中一点就是在于,我没法将一个JsonValue转化为范型类型——它只为Int32,String等几种特定类型定义了隐式转换,又无法得到以object类型所引用的值。不过这也难不到拥有“在运行时创建自定义表达式树并编译成动态代码”的.NET程序员。例如我们可以写这样一个辅助类进行JsonValue至任意类型的转化操作,.NET类库会负责为我们选择合适的转换方式:

class JsonConverter<T>
{
    static JsonConverter()
    {
        var jsonValueExpr = Expression.Parameter(typeof(JsonValue), "jsonValue");
        var convertExpr = Expression.Convert(jsonValueExpr, typeof(T));
        var lambdaExpr = Expression.Lambda<Func<JsonValue, T>>(convertExpr, jsonValueExpr);
        s_convert = lambdaExpr.Compile();
    }

    private static Func<JsonValue, T> s_convert;

    public static T Convert(JsonValue jsonValue)
    {
        return s_convert(jsonValue);
    }
}

泛型参数字典和动态代码生成都是性能的有效保证,而构造一颗我们需要的表达式树也十分容易。这个方法我以前也谈起过,我认为每个称职的.NET程序员都应该熟练掌握这种“通用操作”的实现方式。不过我最近在构造这种类型转换表达式的时候遇到了一些问题。例如,如果我们需要构造一个表达式,将一个JsonPrimitive(而不是先前的JsonValue)转化为int,这便会抛出一个异常,提示我们“没有合适的强制转化方式”。

我猜,可能是因为那些隐式转化操作定义在JsonValue上吧。那么这算不算一个Bug?我认为是,因为与它等价的代码是没有任何问题的,例如:

var value = (int)(new JsonPrimitive(10));

C#编译器会为我们找到JsonValue上定义的op_Implict方法并生成调用代码,而.NET类库却不会这么做,双方配合不够默契。不过其实这很容易绕开,只是我们要将这样的代码:

var convertExpr = Expression.Convert(instanceExpr, targetType);

修改为:

static Expression GetConvertExpression(Expression instanceExpr, Type targetType)
{
    var mediateType = instanceExpr.Type;

    if (mediateType == typeof(object))
    {
        // (TargetType)instance
        return Expression.Convert(instanceExpr, targetType);
    }

    while (mediateType != typeof(object))
    {
        try
        {
            // (MediateType)instace
            var mediateExpr = Expression.Convert(instanceExpr, mediateType);
            // (TargetType)(MediateType)instance
            return Expression.Convert(mediateExpr, targetType);
        }
        catch
        {
            mediateType = mediateType.BaseType;
        }
    }

    throw new Exception(...);
}

我们选择的策略很简单,提供一个“过渡类型”,先将instance转换为过渡类型,再转换成目标类型。如果发生转换错误,则会继续尝试其父类,直至object类型为止,则抛出异常表示转换失败。目前看来这个做法并没有什么问题。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

6 条回复

  1. LeonWeng
    123.6.226.*
    链接

    LeonWeng 2010-10-18 09:29:47

    有点绕,为什么我没有用到这些呢?使用场景在哪里呢

  2. 老赵
    admin
    链接

    老赵 2010-10-18 09:34:21

    @LeonWeng

    用于优化反射性能?我觉得你可以多想一步,力争把代码写的尽可能的漂亮,然后就会发现到处是可以用到反射和表达式树的地方。

  3. 链接

    陈梓瀚(vczh) 2010-10-19 10:54:13

    但是.NET类库是给很多语言用的啊,怎么能支持所有C#额外提供给你的方便功能呢?这个时候你应该用古老的CodeDom哈,那个就是设计来给你这么搞的。

  4. 老赵
    admin
    链接

    老赵 2010-10-19 15:41:01

    @陈梓瀚(vczh)

    现在问题不是“Compile不识别op_Implict方法”,而是它不识别“父类中定义的”隐式转换,这是个问题啊。如果它根本连op_Implict都不识别,就像F#那样,倒真可以用你的说法来解释了……

  5. 陈梓瀚(vczh)
    58.38.48.*
    链接

    陈梓瀚(vczh) 2010-10-23 22:52:29

    你要知道op_Implicit是定义为“SpecialName”的啊,你在反射里面就可以看得出来。嗯嗯,我们知道,如何处理SpecialName的函数,是语言自己规定的……

  6. 老赵
    admin
    链接

    老赵 2010-10-24 03:08:31

    @陈梓瀚(vczh)

    我当然知道这个,估计你没看懂我的意思。我再说一遍吧:如果它“不管哪儿定义了op_Implicit方法都无效”,那我就能够接受。但是,它现在的表现是“直接定义有效,父类定义无效”,我就觉得做得残缺了。而且,既然它是为了C#和VB的Lambda表达式设计的Expression Tree,从情理上也该和C#和VB相同,现在做得不同,还是残缺式的,所以我觉得这应该算作个bug。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我