之前我提到说System.Json是一个十分不好用的类库,其中一点就是在于,我没法将一个JsonValue转化为范型类型――它只为Int32,String等几种特定类型定义了隐式转换,又无法得到以object类型所引用的值。不过这也难不到拥有“在运行时创建自定义表达式树并编译成动态代码”的.NET程序员。例如我们可以写这样一个辅助类进行JsonValue至任意类型的转化操作,.NET类库会负责为我们选择合适的转换方式:
class JsonConverter
{
static JsonConverter()
{
var jsonValueExpr = Expression.Parameter(typeof(JsonValue), "jsonValue");
var convertExpr = Expression.Convert(jsonValueExpr, typeof(T));
var lambdaExpr = Expression.Lambda>(convertExpr, jsonValueExpr);
s_convert = lambdaExpr.Compile();
}
private static Func 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类型为止,则抛出异常表示转换失败。目前看来这个做法并没有什么问题。