最近在写一段代码的时候,为了兼容各种未知的类型,以及完成一个根据类型自动分派的任务到对应的处理器时,使用了这样的一个契约:
用一个object数组来转递值,每个处理器则声明一个可以处理的类型组合。
然后在写如何分派时,发生了一段小插曲。
分派原则
首先,明确一下分派的原则,如果处理器声明能处理的类型是:
string,int
那么只有当对象数组的长度为2,并且类型分别为string和int时,才会分派到这个处理器上去执行,不过,要注意的一点是,string是引用类型,因此值可以为null,最终,声明为string,int的处理器可以接受下列参数:
"aaa",123
null,123
其他形式的参数,不应该分派给该处理器。
然后,再说说更复杂的情况,如果处理器声明能处理的类型是:
IClonable,int?
那么,问题要稍微复杂一点,对于第一个参数而言,要求是任意实现了IClonable接口的对象(例如任何string,数组),当然null也是允许的。对于第二个参数而言,int?可以接受任意int或者null。
综合起来,也就是下列值是允许的:
"aaa”, 123
new int[0], null
null, null
最后,给两个特殊的契约,如果可以传递任意参数,那么就指定object,如果必须某一项为null,则类型指定null。
基础实现
统一好上面的契约,开始实现时,发现事情没有想象中这么简单,对于值为null的情况,需要判断类型是否是引用类型(!Type.IsValueType),如果是值类型,还要额外允许可空类型(Nullable.GetUnderlyingType)。
对于非可空的情况,则判断继承树(Type.IsAssignableFrom),在加上其他的逻辑,代码也不短,更要命的是其中用到了N多的反射,而且每次都要反射一把,性能绝对是个问题。
(如果各位有兴趣,可以自己实现一下,这里就不写了)
思路
解决这些问题之前,先想一下,如果这些类型在编译时已知,那么代码会怎么写哪?
假设需要判定对象obj中间的类型是不是string,那么会写下面的代码:
obj is string
是不是IClonable,那么会写下面的代码:
obj is IClonable
是不是int,那么会写下面的代码:
obj is int
是不是int?,那么会写下面的代码:
obj is int?
看到这里发现什么了?在c#中这些判定惊人的一致,唯一需要特殊处理的是null。所以想象一下,如果能把动态的类型实例,转换成静态的类型信息,对object数组的类型判定将变得非常简单。
进阶实现
那么运用什么手段可以将动态类型实例变成静态的类型信息哪?
答案有很多,当然都围绕一个主题——动态代码生成
这里用最轻量级的动态代码生成方式Emit实现的,具体过程就略过了,最终的核心代码部分如下:
public static Func<object[], bool> CreateFastTypeChecker(this Type[] expectedTypes) { // check parameters ... DynamicMethod dm = new DynamicMethod("Zhenway's Fast Type Checker", typeof(bool), new Type[] { typeof(object[]) }); var il = dm.GetILGenerator(); Label retFalse = il.DefineLabel(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldlen); il.Emit(OpCodes.Ldc_I4, expectedTypes.Length); il.Emit(OpCodes.Bne_Un, RetFalse); for (int index = 0; index < expectedTypes.Length; index++) { bool mustBeNull = expectedTypes[index] == null; bool mustNotBeNull = !mustBeNull && expectedTypes[index].IsValueType && Nullable.GetUnderlyingType(expectedTypes[index]) == null; if (expectedTypes[index] == typeof(object)) continue; Label Next = il.DefineLabel(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldc_I4, index); il.Emit(OpCodes.Ldelem, typeof(object)); il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Cgt_Un); if (mustNotBeNull) il.Emit(OpCodes.Brfalse, retFalse); else il.Emit(OpCodes.Brfalse_S, Next); if (mustBeNull) { il.Emit(OpCodes.Br, RetFalse); } else { il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldc_I4, index); il.Emit(OpCodes.Ldelem, typeof(object)); il.Emit(OpCodes.Isinst, expectedTypes[index]); il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Cgt_Un); il.Emit(OpCodes.Brfalse, RetFalse); } il.MarkLabel(Next); } il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Ret); il.MarkLabel(retFalse); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Ret); return (Func<object[], bool>)dm.CreateDelegate(typeof(Func<object[], bool>)); }
整个过程只需要花50行左右的代码,但是却可以带来非常高的执行效率(当然,创建成本比较高,如果,只跑一两次,那就亏了)。